blob: fb2015c73f19280a7474534d4c7e44335299ff60 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <inttypes.h>
#include "os/os.h"
#include "hal/hal_uart.h"
#include "bsp/bsp.h"
#include "console/console.h"
int g_console_is_init;
/** Indicates whether the previous line of output was completed. */
int console_is_midline;
#define CONSOLE_TX_BUF_SZ 32 /* IO buffering, must be power of 2 */
#define CONSOLE_RX_BUF_SZ 128
#define CONSOLE_RX_CHUNK 16
#define CONSOLE_DEL 0x7f /* del character */
#define CONSOLE_ESC 0x1b /* esc character */
#define CONSOLE_LEFT 'D' /* esc-[-D emitted when moving left */
#define CONSOLE_UP 'A' /* esc-[-A moving up */
#define CONSOLE_RIGHT 'C' /* esc-[-C moving right */
#define CONSOLE_DOWN 'B' /* esc-[-B moving down */
#define CONSOLE_HEAD_INC(cr) (((cr)->cr_head + 1) & ((cr)->cr_size - 1))
#define CONSOLE_TAIL_INC(cr) (((cr)->cr_tail + 1) & ((cr)->cr_size - 1))
typedef void (*console_write_char)(char);
struct console_ring {
uint8_t cr_head;
uint8_t cr_tail;
uint8_t cr_size;
uint8_t _pad;
uint8_t *cr_buf;
};
struct console_tty {
struct console_ring ct_tx;
uint8_t ct_tx_buf[CONSOLE_TX_BUF_SZ]; /* must be after console_ring */
struct console_ring ct_rx;
uint8_t ct_rx_buf[CONSOLE_RX_BUF_SZ]; /* must be after console_ring */
console_rx_cb ct_rx_cb; /* callback that input is ready */
console_write_char ct_write_char;
uint8_t ct_echo_off:1;
uint8_t ct_esc_seq:2;
} console_tty;
static void
console_add_char(struct console_ring *cr, char ch)
{
cr->cr_buf[cr->cr_head] = ch;
cr->cr_head = CONSOLE_HEAD_INC(cr);
}
static uint8_t
console_pull_char(struct console_ring *cr)
{
uint8_t ch;
ch = cr->cr_buf[cr->cr_tail];
cr->cr_tail = CONSOLE_TAIL_INC(cr);
return ch;
}
static int
console_pull_char_head(struct console_ring *cr)
{
if (cr->cr_head != cr->cr_tail) {
cr->cr_head = (cr->cr_head - 1) & (cr->cr_size - 1);
return 0;
} else {
return -1;
}
}
static void
console_queue_char(char ch)
{
struct console_tty *ct = &console_tty;
int sr;
OS_ENTER_CRITICAL(sr);
while (CONSOLE_HEAD_INC(&ct->ct_tx) == ct->ct_tx.cr_tail) {
/* TX needs to drain */
hal_uart_start_tx(CONSOLE_UART);
OS_EXIT_CRITICAL(sr);
if (os_started()) {
os_time_delay(1);
}
OS_ENTER_CRITICAL(sr);
}
console_add_char(&ct->ct_tx, ch);
OS_EXIT_CRITICAL(sr);
}
static void
console_blocking_tx(char ch)
{
hal_uart_blocking_tx(CONSOLE_UART, ch);
}
/*
* Flush cnt characters from console output queue.
*/
static void
console_tx_flush(struct console_tty *ct, int cnt)
{
int i;
uint8_t byte;
for (i = 0; i < cnt; i++) {
if (ct->ct_tx.cr_head == ct->ct_tx.cr_tail) {
/*
* Queue is empty.
*/
break;
}
byte = console_pull_char(&ct->ct_tx);
console_blocking_tx(byte);
}
}
void
console_blocking_mode(void)
{
struct console_tty *ct = &console_tty;
int sr;
OS_ENTER_CRITICAL(sr);
ct->ct_write_char = console_blocking_tx;
console_tx_flush(ct, CONSOLE_TX_BUF_SZ);
OS_EXIT_CRITICAL(sr);
}
void
console_echo(int on)
{
struct console_tty *ct = &console_tty;
ct->ct_echo_off = !on;
}
size_t
console_file_write(void *arg, const char *str, size_t cnt)
{
struct console_tty *ct = &console_tty;
int i;
if (!ct->ct_write_char) {
return cnt;
}
for (i = 0; i < cnt; i++) {
if (str[i] == '\n') {
ct->ct_write_char('\r');
}
ct->ct_write_char(str[i]);
}
if (cnt > 0) {
console_is_midline = str[cnt - 1] != '\n';
}
hal_uart_start_tx(CONSOLE_UART);
return cnt;
}
void
console_write(const char *str, int cnt)
{
console_file_write(NULL, str, cnt);
}
int
console_read(char *str, int cnt, int *newline)
{
struct console_tty *ct = &console_tty;
struct console_ring *cr = &ct->ct_rx;
int sr;
int i;
uint8_t ch;
*newline = 0;
OS_ENTER_CRITICAL(sr);
for (i = 0; i < cnt; i++) {
if (cr->cr_head == cr->cr_tail) {
break;
}
if ((i & (CONSOLE_RX_CHUNK - 1)) == (CONSOLE_RX_CHUNK - 1)) {
/*
* Make a break from blocking interrupts during the copy.
*/
OS_EXIT_CRITICAL(sr);
OS_ENTER_CRITICAL(sr);
}
ch = console_pull_char(cr);
if (ch == '\n') {
*str = '\0';
*newline = 1;
break;
}
*str++ = ch;
}
OS_EXIT_CRITICAL(sr);
if (i > 0 || *newline) {
hal_uart_start_rx(CONSOLE_UART);
}
return i;
}
/*
* Interrupts disabled when console_tx_char/console_rx_char are called.
*/
static int
console_tx_char(void *arg)
{
struct console_tty *ct = (struct console_tty *)arg;
struct console_ring *cr = &ct->ct_tx;
if (cr->cr_head == cr->cr_tail) {
/*
* No more data.
*/
return -1;
}
return console_pull_char(cr);
}
static int
console_buf_space(struct console_ring *cr)
{
int space;
space = (cr->cr_tail - cr->cr_head) & (cr->cr_size - 1);
return space - 1;
}
static int
console_rx_char(void *arg, uint8_t data)
{
struct console_tty *ct = (struct console_tty *)arg;
struct console_ring *tx = &ct->ct_tx;
struct console_ring *rx = &ct->ct_rx;
int tx_space;
int i;
int tx_buf[3];
if (CONSOLE_HEAD_INC(&ct->ct_rx) == ct->ct_rx.cr_tail) {
/*
* RX queue full. Reader must drain this.
*/
if (ct->ct_rx_cb) {
ct->ct_rx_cb();
}
return -1;
}
/* echo */
switch (data) {
case '\r':
case '\n':
/*
* linefeed
*/
tx_buf[0] = '\n';
tx_buf[1] = '\r';
tx_space = 2;
console_add_char(rx, '\n');
if (ct->ct_rx_cb) {
ct->ct_rx_cb();
}
break;
case CONSOLE_ESC:
ct->ct_esc_seq = 1;
goto out;
case '[':
if (ct->ct_esc_seq == 1) {
ct->ct_esc_seq = 2;
goto out;
} else {
goto queue_char;
}
break;
case CONSOLE_LEFT:
if (ct->ct_esc_seq == 2) {
goto backspace;
} else {
goto queue_char;
}
break;
case CONSOLE_UP:
case CONSOLE_DOWN:
if (ct->ct_esc_seq == 2) {
/*
* Do nothing.
*/
ct->ct_esc_seq = 0;
goto out;
}
goto queue_char;
case CONSOLE_RIGHT:
if (ct->ct_esc_seq == 2) {
data = ' '; /* add space */
}
goto queue_char;
case '\b':
case CONSOLE_DEL:
backspace:
/*
* backspace
*/
ct->ct_esc_seq = 0;
if (console_pull_char_head(rx) == 0) {
/*
* Only wipe out char if we can pull stuff off from head.
*/
tx_buf[0] = '\b';
tx_buf[1] = ' ';
tx_buf[2] = '\b';
tx_space = 3;
} else {
goto out;
}
break;
default:
queue_char:
tx_buf[0] = data;
tx_space = 1;
ct->ct_esc_seq = 0;
console_add_char(rx, data);
break;
}
if (!ct->ct_echo_off) {
if (console_buf_space(tx) < tx_space) {
console_tx_flush(ct, tx_space);
}
for (i = 0; i < tx_space; i++) {
console_add_char(tx, tx_buf[i]);
}
hal_uart_start_tx(CONSOLE_UART);
}
out:
return 0;
}
int
console_is_init(void)
{
return (g_console_is_init);
}
int
console_init(console_rx_cb rx_cb)
{
struct console_tty *ct = &console_tty;
int rc;
rc = hal_uart_init_cbs(CONSOLE_UART, console_tx_char, NULL,
console_rx_char, ct);
if (rc) {
return rc;
}
ct->ct_tx.cr_size = CONSOLE_TX_BUF_SZ;
ct->ct_tx.cr_buf = ct->ct_tx_buf;
ct->ct_rx.cr_size = CONSOLE_RX_BUF_SZ;
ct->ct_rx.cr_buf = ct->ct_rx_buf;
ct->ct_rx_cb = rx_cb;
ct->ct_write_char = console_queue_char;
rc = hal_uart_config(CONSOLE_UART, 115200, 8, 1, HAL_UART_PARITY_NONE,
HAL_UART_FLOW_CTL_NONE);
if (rc) {
return rc;
}
g_console_is_init = 1;
return 0;
}