blob: e95e7ed377c9b22656e068cd3a19050eb9caab71 [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 "os/mynewt.h"
#include "hal/hal_uart.h"
#include "bsp/bsp.h"
#ifdef MN_LINUX
#include <pty.h>
#endif
#ifdef MN_OSX
#include <util.h>
#endif
#ifdef MN_FreeBSD
#include <libutil.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <errno.h>
#include "mcu/mcu_sim.h"
#include "native_uart_cfg_priv.h"
#define UART_CNT 2
#if MYNEWT_VAL(CONSOLE_UART_TX_BUF_SIZE)
#define UART_MAX_BYTES_PER_POLL MYNEWT_VAL(CONSOLE_UART_TX_BUF_SIZE) - 2
#else
#define UART_MAX_BYTES_PER_POLL 64
#endif
#define UART_POLLER_STACK_SZ OS_STACK_ALIGN(1024)
struct uart {
int u_open;
int u_fd;
int u_tx_run;
int u_rx_char;
hal_uart_rx_char u_rx_func;
hal_uart_tx_char u_tx_func;
hal_uart_tx_done u_tx_done;
void *u_func_arg;
};
const char *native_uart_dev_strs[UART_CNT];
/*
* XXXX should try to use O_ASYNC/SIGIO for byte arrival notification,
* so we wouldn't need an OS for pseudo ttys.
*/
char *native_uart_log_file = NULL;
static int uart_log_fd = -1;
static struct uart uarts[UART_CNT];
static int uart_poller_running;
static struct os_task uart_poller_task;
static os_stack_t uart_poller_stack[UART_POLLER_STACK_SZ];
static void
uart_open_log(void)
{
if (native_uart_log_file && uart_log_fd < 0) {
uart_log_fd = open(native_uart_log_file, O_WRONLY | O_CREAT | O_TRUNC,
0666);
assert(uart_log_fd >= 0);
}
}
static void
uart_log_data(struct uart *u, int istx, uint8_t data)
{
static struct {
struct uart *uart;
int istx;
uint32_t time;
int chars_in_line;
} state = {
.uart = NULL,
.istx = 0
};
uint32_t now;
char tmpbuf[32];
int len;
if (uart_log_fd < 0) {
return;
}
now = os_time_get();
if (state.uart) {
if (u != state.uart || now != state.time || istx != state.istx) {
/*
* End current printout.
*/
if (write(uart_log_fd, "\n", 1) != 1) {
assert(0);
}
state.uart = NULL;
} else {
if (state.chars_in_line == 8) {
if (write(uart_log_fd, "\n\t", 2) != 2) {
assert(0);
}
state.chars_in_line = 0;
}
len = snprintf(tmpbuf, sizeof(tmpbuf), "%c (%02x) ",
isalnum(data) ? data : '?', data);
if (write(uart_log_fd, tmpbuf, len) != len) {
assert(0);
}
state.chars_in_line++;
}
}
if (u && state.uart == NULL) {
len = snprintf(tmpbuf, sizeof(tmpbuf), "%u:uart%d %s\n\t%c (%02x) ",
now, u - uarts, istx ? "tx" : "rx", isalnum(data) ? data : '?', data);
if (write(uart_log_fd, tmpbuf, len) != len) {
assert(0);
}
state.chars_in_line = 1;
state.uart = u;
state.istx = istx;
state.time = now;
}
}
static int
uart_transmit_char(struct uart *uart)
{
int sr;
int rc;
char ch;
OS_ENTER_CRITICAL(sr);
rc = uart->u_tx_func(uart->u_func_arg);
if (rc < 0) {
/*
* No more data to send.
*/
uart->u_tx_run = 0;
if (uart->u_tx_done) {
uart->u_tx_done(uart->u_func_arg);
}
OS_EXIT_CRITICAL(sr);
return 0;
}
ch = rc;
uart_log_data(uart, 1, ch);
OS_EXIT_CRITICAL(sr);
rc = write(uart->u_fd, &ch, 1);
if (rc <= 0) {
/* XXX EOF/error, what now? */
return -1;
}
return 0;
}
static void
uart_poller(void *arg)
{
int i;
int rc;
int bytes;
int sr;
int didwork;
unsigned char ch;
struct uart *uart;
while (1) {
for (i = 0; i < UART_CNT; i++) {
if (!uarts[i].u_open) {
continue;
}
uart = &uarts[i];
for (bytes = 0; bytes < UART_MAX_BYTES_PER_POLL; bytes++) {
didwork = 0;
if (uart->u_tx_run) {
uart_transmit_char(uart);
didwork = 1;
}
if (uart->u_rx_char < 0) {
rc = read(uart->u_fd, &ch, 1);
if (rc == 0) {
/* XXX EOF, what now? */
assert(0);
} else if (rc > 0) {
uart->u_rx_char = ch;
}
}
if (uart->u_rx_char >= 0) {
OS_ENTER_CRITICAL(sr);
uart_log_data(uart, 0, uart->u_rx_char);
rc = uart->u_rx_func(uart->u_func_arg, uart->u_rx_char);
/* Delivered */
if (rc >= 0) {
uart->u_rx_char = -1;
didwork = 1;
}
OS_EXIT_CRITICAL(sr);
}
if (!didwork) {
break;
}
}
}
uart_log_data(NULL, 0, 0);
os_time_delay(OS_TICKS_PER_SEC / 100);
}
}
static void
set_nonblock(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
if (flags == -1) {
const char msg[] = "fcntl(F_GETFL) fail";
write(1, msg, sizeof(msg));
return;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
const char msg[] = "fcntl(F_SETFL) fail";
write(1, msg, sizeof(msg));
return;
}
}
static int
uart_pty_set_attr(int fd)
{
struct termios tios;
if (tcgetattr(fd, &tios)) {
const char msg[] = "tcgetattr() failed";
write(1, msg, sizeof(msg));
return -1;
}
tios.c_cflag &= ~(CSIZE | CSTOPB | PARENB);
tios.c_cflag |= CS8 | CREAD;
tios.c_iflag = IGNPAR;
tios.c_oflag = 0;
tios.c_lflag = 0;
if (tcsetattr(fd, TCSAFLUSH, &tios) < 0) {
const char msg[] = "tcsetattr() failed";
write(1, msg, sizeof(msg));
return -1;
}
return 0;
}
static int
uart_pty(int port)
{
int fd;
int loop_slave;
char pty_name[32];
char msg[64];
if (openpty(&fd, &loop_slave, pty_name, NULL, NULL) < 0) {
const char msg[] = "openpty() failed";
write(1, msg, sizeof(msg));
return -1;
}
if (uart_pty_set_attr(loop_slave)) {
goto err;
}
snprintf(msg, sizeof(msg), "uart%d at %s\n", port, pty_name);
write(1, msg, strlen(msg));
return fd;
err:
close(fd);
close(loop_slave);
return -1;
}
/**
* Opens an external device terminal (/dev/cu.<...>).
*/
static int
uart_open_dev(int port, int32_t baudrate, uint8_t databits,
uint8_t stopbits, enum hal_uart_parity parity,
enum hal_uart_flow_ctl flow_ctl)
{
const char *filename;
int fd;
int rc;
filename = native_uart_dev_strs[port];
assert(filename != NULL);
fd = open(filename, O_RDWR);
if (fd < 0) {
return -1;
}
rc = uart_dev_set_attr(fd, baudrate, databits,
stopbits, parity, flow_ctl);
if (rc != 0) {
close(fd);
return rc;
}
dprintf(1, "uart%d at %s\n", port, filename);
return fd;
}
void
hal_uart_start_tx(int port)
{
int sr;
if (port >= UART_CNT || uarts[port].u_open == 0) {
return;
}
OS_ENTER_CRITICAL(sr);
uarts[port].u_tx_run = 1;
if (!os_started()) {
/*
* XXX this is a hack.
*/
uart_transmit_char(&uarts[port]);
}
OS_EXIT_CRITICAL(sr);
}
void
hal_uart_start_rx(int port)
{
/* nothing to do here */
}
void
hal_uart_blocking_tx(int port, uint8_t data)
{
if (port >= UART_CNT || uarts[port].u_open == 0) {
return;
}
/* XXX: Count statistics and add error checking here. */
(void) write(uarts[port].u_fd, &data, sizeof(data));
}
int
hal_uart_init_cbs(int port, hal_uart_tx_char tx_func, hal_uart_tx_done tx_done,
hal_uart_rx_char rx_func, void *arg)
{
struct uart *uart;
int rc;
if (port >= UART_CNT) {
return -1;
}
uart = &uarts[port];
if (uart->u_open) {
return -1;
}
uart->u_tx_func = tx_func;
uart->u_tx_done = tx_done;
uart->u_rx_func = rx_func;
uart->u_func_arg = arg;
uart->u_rx_char = -1;
if (!uart_poller_running) {
uart_poller_running = 1;
rc = os_task_init(&uart_poller_task, "uartpoll", uart_poller, NULL,
MYNEWT_VAL(MCU_UART_POLLER_PRIO), OS_WAIT_FOREVER, uart_poller_stack,
UART_POLLER_STACK_SZ);
assert(rc == 0);
}
return 0;
}
int
hal_uart_config(int port, int32_t baudrate, uint8_t databits, uint8_t stopbits,
enum hal_uart_parity parity, enum hal_uart_flow_ctl flow_ctl)
{
struct uart *uart;
if (port >= UART_CNT) {
return -1;
}
uart = &uarts[port];
if (uart->u_open) {
return -1;
}
if (native_uart_dev_strs[port] == NULL) {
uart->u_fd = uart_pty(port);
} else {
uart->u_fd = uart_open_dev(port, baudrate, databits, stopbits,
parity, flow_ctl);
}
if (uart->u_fd < 0) {
return -1;
}
set_nonblock(uart->u_fd);
uart_open_log();
uart->u_open = 1;
return 0;
}
int
hal_uart_close(int port)
{
struct uart *uart;
int rc;
if (port >= UART_CNT) {
rc = -1;
goto err;
}
uart = &uarts[port];
if (!uart->u_open) {
rc = -1;
goto err;
}
close(uart->u_fd);
uart->u_open = 0;
return (0);
err:
return (rc);
}
int
hal_uart_init(int port, void *arg)
{
return (0);
}
int
uart_set_dev(int port, const char *dev_str)
{
if (port < 0 || port >= UART_CNT) {
return SYS_EINVAL;
}
if (uarts[port].u_open) {
return SYS_EBUSY;
}
native_uart_dev_strs[port] = dev_str;
return 0;
}