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;

    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(0);
        }
        return -1;
    }

    tx_space = console_buf_space(tx);
    /* echo */
    switch (data) {
    case '\r':
    case '\n':
        /*
         * linefeed
         */
        if (tx_space < 3) {
            console_tx_flush(ct, 3);
        }
        console_add_char(tx, '\n');
        console_add_char(tx, '\r');
        console_add_char(rx, '\n');
        if (ct->ct_rx_cb) {
            ct->ct_rx_cb(1);
        }
        break;
    case '\b':
        /*
         * backspace
         */
        if (tx_space < 3) {
            console_tx_flush(ct, 3);
        }
        console_add_char(tx, '\b');
        console_add_char(tx, ' ');
        console_add_char(tx, '\b');
        console_pull_char_head(rx);
        break;
    default:
        if (tx_space < 1) {
            console_tx_flush(ct, 1);
        }
        console_add_char(tx, data);
        console_add_char(rx, data);
        break;
    }
    hal_uart_start_tx(CONSOLE_UART);
    return 0;
}
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;
}
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 = 0;
    int i;
#if MYNEWT_VAL(CONSOLE_HIST_ENABLE)
    uint8_t tx_buf[MYNEWT_VAL(CONSOLE_RX_BUF_SIZE)];
#else
    uint8_t tx_buf[3];
#endif

    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 MYNEWT_VAL(CONSOLE_HIST_ENABLE)
        console_hist_add(rx);
#endif
        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) {
            goto queue_char;
        }
#if MYNEWT_VAL(CONSOLE_HIST_ENABLE)
        tx_space = console_hist_move(rx, tx_buf, data);
        tx_buf[tx_space] = 0;
        ct->ct_esc_seq = 0;
        /*
         * when moving up, stop on oldest history entry
         * when moving down, let it delete input before leaving...
         */
        if (data == CONSOLE_UP && tx_space == 0) {
            goto out;
        }
        if (!ct->ct_echo_off) {
            /* HACK: clean line by backspacing up to maximum possible space */
            for (i = 0; i < MYNEWT_VAL(CONSOLE_TX_BUF_SIZE); i++) {
                if (console_buf_space(tx) < 3) {
                    console_tx_flush(ct, 3);
                }
                console_add_char(tx, '\b');
                console_add_char(tx, ' ');
                console_add_char(tx, '\b');
                uart_start_tx(ct->ct_dev);
            }
            if (tx_space == 0) {
                goto out;
            }
        } else {
            goto queue_char;
        }
        break;
#else
        ct->ct_esc_seq = 0;
        goto out;
#endif
    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]);
        }
        uart_start_tx(ct->ct_dev);
    }
out:
    return 0;
}