blob: aa8a94c59604d5b21ba0ac1e349576ae3638be30 [file] [log] [blame]
Paul Sokolovskyca8aea12018-09-26 21:09:28 +03001/*
2 * Copyright (c) 2018 Linaro Limited.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
Gerard Marull-Paretas79e6b0e2022-08-25 09:58:46 +02007#include <zephyr/kernel.h>
Gerard Marull-Paretas5113c142022-05-06 11:12:04 +02008#include <zephyr/drivers/uart.h>
9#include <zephyr/sys/printk.h>
10#include <zephyr/console/tty.h>
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030011
Kumar Galaa1b77fd2020-05-27 11:26:57 -050012static int tty_irq_input_hook(struct tty_serial *tty, uint8_t c);
13static int tty_putchar(struct tty_serial *tty, uint8_t c);
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030014
Tomasz Bursztykae18fcbb2020-04-30 20:33:38 +020015static void tty_uart_isr(const struct device *dev, void *user_data)
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030016{
17 struct tty_serial *tty = user_data;
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030018
19 uart_irq_update(dev);
20
21 if (uart_irq_rx_ready(dev)) {
Kumar Galaa1b77fd2020-05-27 11:26:57 -050022 uint8_t c;
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030023
24 while (1) {
25 if (uart_fifo_read(dev, &c, 1) == 0) {
26 break;
27 }
28 tty_irq_input_hook(tty, c);
29 }
30 }
31
32 if (uart_irq_tx_ready(dev)) {
33 if (tty->tx_get == tty->tx_put) {
34 /* Output buffer empty, don't bother
35 * us with tx interrupts
36 */
37 uart_irq_tx_disable(dev);
38 } else {
39 uart_fifo_fill(dev, &tty->tx_ringbuf[tty->tx_get++], 1);
40 if (tty->tx_get >= tty->tx_ringbuf_sz) {
Patrik Flykt24d71432019-03-26 19:57:45 -060041 tty->tx_get = 0U;
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030042 }
Paul Sokolovsky9d3b48f2018-10-23 20:02:25 +030043 k_sem_give(&tty->tx_sem);
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030044 }
45 }
46}
47
Kumar Galaa1b77fd2020-05-27 11:26:57 -050048static int tty_irq_input_hook(struct tty_serial *tty, uint8_t c)
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030049{
50 int rx_next = tty->rx_put + 1;
51
52 if (rx_next >= tty->rx_ringbuf_sz) {
53 rx_next = 0;
54 }
55
56 if (rx_next == tty->rx_get) {
57 /* Try to give a clue to user that some input was lost */
Paul Sokolovsky1165e852018-11-12 18:17:42 +030058 tty_putchar(tty, '~');
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030059 return 1;
60 }
61
62 tty->rx_ringbuf[tty->rx_put] = c;
63 tty->rx_put = rx_next;
64 k_sem_give(&tty->rx_sem);
65
66 return 1;
67}
68
Kumar Galaa1b77fd2020-05-27 11:26:57 -050069static int tty_putchar(struct tty_serial *tty, uint8_t c)
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030070{
71 unsigned int key;
72 int tx_next;
Paul Sokolovsky9d3b48f2018-10-23 20:02:25 +030073 int res;
74
Joakim Andersson1738f0e2020-06-16 14:48:47 +020075 res = k_sem_take(&tty->tx_sem,
76 k_is_in_isr() ? K_NO_WAIT :
77 SYS_TIMEOUT_MS(tty->tx_timeout));
Paul Sokolovsky9d3b48f2018-10-23 20:02:25 +030078 if (res < 0) {
79 return res;
80 }
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030081
82 key = irq_lock();
83 tx_next = tty->tx_put + 1;
84 if (tx_next >= tty->tx_ringbuf_sz) {
85 tx_next = 0;
86 }
87 if (tx_next == tty->tx_get) {
88 irq_unlock(key);
Paul Sokolovsky62030202018-10-23 21:15:46 +030089 return -ENOSPC;
Paul Sokolovskyca8aea12018-09-26 21:09:28 +030090 }
91
92 tty->tx_ringbuf[tty->tx_put] = c;
93 tty->tx_put = tx_next;
94
95 irq_unlock(key);
96 uart_irq_tx_enable(tty->uart_dev);
97 return 0;
98}
99
Paul Sokolovsky62030202018-10-23 21:15:46 +0300100ssize_t tty_write(struct tty_serial *tty, const void *buf, size_t size)
101{
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500102 const uint8_t *p = buf;
Paul Sokolovsky62030202018-10-23 21:15:46 +0300103 size_t out_size = 0;
104 int res = 0;
105
Patrik Flykt24d71432019-03-26 19:57:45 -0600106 if (tty->tx_ringbuf_sz == 0U) {
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300107 /* Unbuffered operation, implicitly blocking. */
108 out_size = size;
109
110 while (size--) {
111 uart_poll_out(tty->uart_dev, *p++);
112 }
113
114 return out_size;
115 }
116
Paul Sokolovsky62030202018-10-23 21:15:46 +0300117 while (size--) {
118 res = tty_putchar(tty, *p++);
119 if (res < 0) {
120 /* If we didn't transmit anything, return the error. */
121 if (out_size == 0) {
122 errno = -res;
123 return res;
124 }
125
126 /*
127 * Otherwise, return how much we transmitted. If error
128 * was transient (like EAGAIN), on next call user might
129 * not even get it. And if it's non-transient, they'll
130 * get it on the next call.
131 */
132 return out_size;
133 }
134
135 out_size++;
136 }
137
138 return out_size;
139}
140
141static int tty_getchar(struct tty_serial *tty)
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300142{
143 unsigned int key;
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500144 uint8_t c;
Paul Sokolovsky680085b2018-10-22 21:01:22 +0300145 int res;
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300146
Carles Cufi8e297e82020-05-04 12:58:46 +0200147 res = k_sem_take(&tty->rx_sem, SYS_TIMEOUT_MS(tty->rx_timeout));
Paul Sokolovsky680085b2018-10-22 21:01:22 +0300148 if (res < 0) {
149 return res;
150 }
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300151
152 key = irq_lock();
153 c = tty->rx_ringbuf[tty->rx_get++];
154 if (tty->rx_get >= tty->rx_ringbuf_sz) {
Patrik Flykt24d71432019-03-26 19:57:45 -0600155 tty->rx_get = 0U;
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300156 }
157 irq_unlock(key);
158
159 return c;
160}
161
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300162static ssize_t tty_read_unbuf(struct tty_serial *tty, void *buf, size_t size)
163{
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500164 uint8_t *p = buf;
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300165 size_t out_size = 0;
166 int res = 0;
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500167 uint32_t timeout = tty->rx_timeout;
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300168
169 while (size) {
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500170 uint8_t c;
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300171 res = uart_poll_in(tty->uart_dev, &c);
172 if (res <= -2) {
Anas Nashiff2cb20c2019-06-18 14:45:40 -0400173 /* Error occurred, best we can do is to return
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300174 * accumulated data w/o error, or return error
175 * directly if none.
176 */
177 if (out_size == 0) {
178 errno = res;
179 return -1;
180 }
181 break;
182 }
183
184 if (res == 0) {
185 *p++ = c;
186 out_size++;
187 size--;
188 }
189
Andy Ross32bb2392020-03-05 14:54:28 -0800190 if (size == 0 ||
Carles Cufi8e297e82020-05-04 12:58:46 +0200191 ((timeout != SYS_FOREVER_MS) && timeout-- == 0U)) {
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300192 break;
193 }
194
195 /* Avoid 100% busy-polling, and yet try to process bursts
196 * of data without extra delays.
197 */
198 if (res == -1) {
Peter Bigot6e5db352019-10-06 14:02:31 -0500199 k_sleep(K_MSEC(1));
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300200 }
201 }
202
203 return out_size;
204}
205
Paul Sokolovsky62030202018-10-23 21:15:46 +0300206ssize_t tty_read(struct tty_serial *tty, void *buf, size_t size)
207{
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500208 uint8_t *p = buf;
Paul Sokolovsky62030202018-10-23 21:15:46 +0300209 size_t out_size = 0;
210 int res = 0;
211
Patrik Flykt24d71432019-03-26 19:57:45 -0600212 if (tty->rx_ringbuf_sz == 0U) {
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300213 return tty_read_unbuf(tty, buf, size);
214 }
215
Paul Sokolovsky62030202018-10-23 21:15:46 +0300216 while (size--) {
217 res = tty_getchar(tty);
218 if (res < 0) {
219 /* If we didn't transmit anything, return the error. */
220 if (out_size == 0) {
221 errno = -res;
222 return res;
223 }
224
225 /*
226 * Otherwise, return how much we transmitted. If error
227 * was transient (like EAGAIN), on next call user might
228 * not even get it. And if it's non-transient, they'll
229 * get it on the next call.
230 */
231 return out_size;
232 }
233
Kumar Galaa1b77fd2020-05-27 11:26:57 -0500234 *p++ = (uint8_t)res;
Paul Sokolovsky62030202018-10-23 21:15:46 +0300235 out_size++;
236 }
237
238 return out_size;
239}
240
Tomasz Bursztykae18fcbb2020-04-30 20:33:38 +0200241int tty_init(struct tty_serial *tty, const struct device *uart_dev)
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300242{
Pavel Kral51eb4572019-07-15 19:02:29 +0200243 if (!uart_dev) {
244 return -ENODEV;
245 }
246
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300247 tty->uart_dev = uart_dev;
Paul Sokolovsky09254112018-12-05 12:07:38 +0300248
249 /* We start in unbuffer mode. */
250 tty->rx_ringbuf = NULL;
Patrik Flykt24d71432019-03-26 19:57:45 -0600251 tty->rx_ringbuf_sz = 0U;
Paul Sokolovsky09254112018-12-05 12:07:38 +0300252 tty->tx_ringbuf = NULL;
Patrik Flykt24d71432019-03-26 19:57:45 -0600253 tty->tx_ringbuf_sz = 0U;
Paul Sokolovsky09254112018-12-05 12:07:38 +0300254
Patrik Flykt24d71432019-03-26 19:57:45 -0600255 tty->rx_get = tty->rx_put = tty->tx_get = tty->tx_put = 0U;
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300256
Carles Cufi8e297e82020-05-04 12:58:46 +0200257 tty->rx_timeout = SYS_FOREVER_MS;
258 tty->tx_timeout = SYS_FOREVER_MS;
Paul Sokolovsky680085b2018-10-22 21:01:22 +0300259
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300260 uart_irq_callback_user_data_set(uart_dev, tty_uart_isr, tty);
Paul Sokolovsky09254112018-12-05 12:07:38 +0300261
262 return 0;
Paul Sokolovskyca8aea12018-09-26 21:09:28 +0300263}
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300264
265int tty_set_rx_buf(struct tty_serial *tty, void *buf, size_t size)
266{
267 uart_irq_rx_disable(tty->uart_dev);
268
269 tty->rx_ringbuf = buf;
270 tty->rx_ringbuf_sz = size;
271
272 if (size > 0) {
James Harrisb1042812021-03-03 12:02:05 -0800273 k_sem_init(&tty->rx_sem, 0, K_SEM_MAX_LIMIT);
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300274 uart_irq_rx_enable(tty->uart_dev);
275 }
276
277 return 0;
278}
279
280int tty_set_tx_buf(struct tty_serial *tty, void *buf, size_t size)
281{
282 uart_irq_tx_disable(tty->uart_dev);
283
284 tty->tx_ringbuf = buf;
285 tty->tx_ringbuf_sz = size;
286
James Harrisb1042812021-03-03 12:02:05 -0800287 k_sem_init(&tty->tx_sem, size - 1, K_SEM_MAX_LIMIT);
Paul Sokolovsky09254112018-12-05 12:07:38 +0300288
Paul Sokolovskya7df3a12018-12-05 11:03:12 +0300289 /* New buffer is initially empty, no need to re-enable interrupts,
290 * it will be done when needed (on first output char).
291 */
292
293 return 0;
294}