blob: 1f94373c8a1242117b62e5b5dfa4997177857366 [file]
/*
* Copyright (c) 2018 Linaro Limited.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/printk.h>
#include <zephyr/console/tty.h>
#include <zephyr/sys/clock.h>
enum tty_signal {
TTY_SIGNAL_RXRDY = BIT(0),
TTY_SIGNAL_TXDONE = BIT(1),
};
static void uart_tx_handle(const struct device *dev, struct tty_serial *tty);
static void uart_rx_handle(const struct device *dev, struct tty_serial *tty);
static void tty_uart_isr(const struct device *dev, void *user_data)
{
struct tty_serial *tty = user_data;
uart_irq_update(dev);
if (uart_irq_rx_ready(dev) > 0) {
uart_rx_handle(dev, tty);
}
if (uart_irq_tx_ready(dev) > 0) {
uart_tx_handle(dev, tty);
}
}
static void uart_rx_handle(const struct device *dev, struct tty_serial *tty)
{
uint8_t *data;
uint32_t len;
int rd_len;
bool new_data = false;
int err;
do {
len = ring_buf_put_claim(&tty->rx_buf, &data, ring_buf_capacity_get(&tty->rx_buf));
if (len > 0) {
rd_len = uart_fifo_read(dev, data, len);
if (rd_len > 0) {
new_data = true;
}
err = ring_buf_put_finish(&tty->rx_buf, rd_len);
__ASSERT_NO_MSG(err == 0);
ARG_UNUSED(err);
if (rd_len < len) {
/* No more data in the FIFO, exit loop. */
break;
}
} else {
uint8_t dummy;
const char dummy_char = '~';
/* Try to give a clue to user that some input was lost */
tty_write(tty, &dummy_char, sizeof(dummy_char));
/* No space in the ring buffer - consume byte. */
rd_len = uart_fifo_read(dev, &dummy, 1);
}
} while (rd_len > 0);
if (new_data) {
k_event_post(&tty->signal_event, TTY_SIGNAL_RXRDY);
}
}
static void uart_tx_handle(const struct device *dev, struct tty_serial *tty)
{
uint32_t len;
uint8_t *data;
int err;
len = ring_buf_get_claim(&tty->tx_buf, &data, ring_buf_capacity_get(&tty->tx_buf));
if (len > 0) {
len = uart_fifo_fill(dev, data, len);
err = ring_buf_get_finish(&tty->tx_buf, len);
__ASSERT_NO_MSG(err == 0);
ARG_UNUSED(err);
} else {
uart_irq_tx_disable(dev);
atomic_clear(&tty->tx_busy);
}
k_event_post(&tty->signal_event, TTY_SIGNAL_TXDONE);
}
ssize_t tty_write(struct tty_serial *tty, const void *buf, size_t size)
{
const uint8_t *p = buf;
ssize_t out_size = 0;
uint32_t write_size;
int res = 0;
if (ring_buf_capacity_get(&tty->tx_buf) == 0U) {
/* Unbuffered operation, implicitly blocking. */
out_size = size;
while (size--) {
uart_poll_out(tty->uart_dev, *p++);
}
return out_size;
}
while (size > 0) {
write_size = ring_buf_put(&tty->tx_buf, p, size);
if (atomic_set(&tty->tx_busy, 1) == 0) {
uart_irq_tx_enable(tty->uart_dev);
}
if (write_size == 0) {
/* Output buffer full, wait for space. */
res = k_event_wait_safe(&tty->signal_event, TTY_SIGNAL_TXDONE, false,
k_is_in_isr() ? K_NO_WAIT : tty->tx_timeout);
if (res == 0) {
break;
}
} else {
out_size += write_size;
p += write_size;
size -= write_size;
}
}
if (out_size == 0) {
errno = -EAGAIN;
return -EAGAIN;
}
return out_size;
}
static ssize_t tty_read_unbuf(struct tty_serial *tty, void *buf, size_t size)
{
uint8_t *p = buf;
size_t out_size = 0;
int res = 0;
k_timepoint_t timeout = sys_timepoint_calc(tty->rx_timeout);
while (size) {
uint8_t c;
res = uart_poll_in(tty->uart_dev, &c);
if (res <= -2) {
/* Error occurred, best we can do is to return
* accumulated data w/o error, or return error
* directly if none.
*/
if (out_size == 0) {
errno = res;
return -1;
}
break;
}
if (res == 0) {
*p++ = c;
out_size++;
size--;
}
if (size == 0 || sys_timepoint_expired(timeout)) {
break;
}
/* Avoid 100% busy-polling, and yet try to process bursts
* of data without extra delays.
*/
if (res == -1) {
k_sleep(K_MSEC(1));
}
}
return out_size;
}
ssize_t tty_read(struct tty_serial *tty, void *buf, size_t size)
{
uint8_t *p = buf;
size_t out_size = 0;
uint32_t read_size;
int res = 0;
if (ring_buf_capacity_get(&tty->rx_buf) == 0U) {
return tty_read_unbuf(tty, buf, size);
}
while (size > 0) {
read_size = ring_buf_get(&tty->rx_buf, p, size);
if (read_size == 0) {
/* Output buffer full, wait for space. */
res = k_event_wait_safe(&tty->signal_event, TTY_SIGNAL_RXRDY, false,
k_is_in_isr() ? K_NO_WAIT : tty->rx_timeout);
if (res == 0) {
break;
}
} else {
out_size += read_size;
p += read_size;
size -= read_size;
}
}
if (out_size == 0) {
errno = -EAGAIN;
return -EAGAIN;
}
return out_size;
}
int tty_init(struct tty_serial *tty, const struct device *uart_dev)
{
if (!uart_dev) {
return -ENODEV;
}
tty->uart_dev = uart_dev;
/* We start in unbuffer mode. */
ring_buf_init(&tty->rx_buf, 0, NULL);
ring_buf_init(&tty->tx_buf, 0, NULL);
tty->rx_timeout = K_FOREVER;
tty->tx_timeout = K_FOREVER;
k_event_init(&tty->signal_event);
tty->tx_busy = 0;
uart_irq_callback_user_data_set(uart_dev, tty_uart_isr, tty);
return 0;
}
int tty_set_rx_buf(struct tty_serial *tty, void *buf, size_t size)
{
uart_irq_rx_disable(tty->uart_dev);
ring_buf_init(&tty->rx_buf, size, buf);
if (size > 0) {
uart_irq_rx_enable(tty->uart_dev);
}
return 0;
}
int tty_set_tx_buf(struct tty_serial *tty, void *buf, size_t size)
{
uart_irq_tx_disable(tty->uart_dev);
ring_buf_init(&tty->tx_buf, size, buf);
/* New buffer is initially empty, no need to re-enable interrupts,
* it will be done when needed (on first output char).
*/
return 0;
}