uart: Add driver for posix arch

Added a new UART driver for posix arch boards.
The driver can be configured to either attach to a new
pseudo-terminal, or to connect to the invoking shell
stdin-out.

Signed-off-by: Alberto Escolar Piedras <alpi@oticon.com>
This commit is contained in:
Alberto Escolar Piedras 2018-09-22 19:19:59 +02:00 committed by Carles Cufí
commit 013f279111
9 changed files with 461 additions and 21 deletions

View file

@ -23,7 +23,7 @@ if (CONFIG_ASAN)
zephyr_ld_options(-fsanitize=address)
endif ()
zephyr_compile_definitions(_POSIX_C_SOURCE=200809 _XOPEN_SOURCE _XOPEN_SOURCE_EXTENDED)
zephyr_compile_definitions(_POSIX_C_SOURCE=200809 _XOPEN_SOURCE=600 _XOPEN_SOURCE_EXTENDED)
zephyr_ld_options(
-ldl

View file

@ -38,10 +38,17 @@ endchoice
endif # BT_HCI
if SERIAL
config UART_NATIVE_POSIX
def_bool y
endif # SERIAL
if LOG
config LOG_BACKEND_NATIVE_POSIX
def_bool y
def_bool y if !SERIAL
# For native_posix we can log immediately without any problem
# Doing so will be nicer for debugging
@ -55,4 +62,14 @@ config LOG_PROCESS_THREAD
endif # LOG
if CONSOLE
config NATIVE_POSIX_CONSOLE
def_bool y if !SERIAL
config UART_CONSOLE
def_bool y if SERIAL
endif #CONSOLE
endif # BOARD_NATIVE_POSIX

View file

@ -19,6 +19,9 @@ you use native host tools for compiling, debugging, and analyzing your
Zephyr application, eliminating the need for architecture-specific
target hardware in the early phases of development.
This board provides a few peripherals such as an Ethernet driver and UART.
See `Peripherals`_ for more information.
Host system dependencies
========================
@ -458,8 +461,8 @@ The following peripherals are currently provided with this board:
``stdout``.
- Feed any input from the native application ``stdin`` to a possible
running :ref:`Shell`. For more information refer to the section
`Shell support`_.
running legacy shell. For more information refer to the section
`Legacy shell support`_.
**Clock, timer and system tick model**
This model provides the system tick timer. By default
@ -538,11 +541,40 @@ The following peripherals are currently provided with this board:
must be powered down and support Bluetooth Low Energy (i.e. support the
Bluetooth specification version 4.0 or greater).
Shell support
*************
**UART**
An optional UART driver can be compiled with native_posix.
For more information refer to the section `UART`_.
When the :ref:`Shell` subsystem is compiled with your application, the native
standard input (``stdin``) will be redirected to the shell.
UART
*****
This driver can be configured to either create and link the UART to a new
pseudoterminal (i.e. ``/dev/pts<nbr>``), or to map the UART input and
output to the executable's ``stdin`` and ``stdout``.
This is chosen by selecting either
:option:`CONFIG_NATIVE_UART_0_ON_OWN_PTY` or
:option:`CONFIG_NATIVE_UART_0_ON_STDINOUT`
Note that for interactive use, the first option should be chosen. The
second option is only intended for automated testing or feeding/piping other
processes output to the UART.
When :option:`CONFIG_NATIVE_UART_0_ON_OWN_PTY` is chosen, the name of the
newly created UART pseudo-terminal will be displayed in the console.
You may also chose to automatically attach a terminal emulator to it by
passing the command line option ``-attach_uart`` to the executable.
The command used for attaching to the new shell can be set with the command line
option ``-attach_uart_cmd=<"cmd">``. Where the default command is given by
:option:`CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD`.
Note that the default command assumes both ``xterm`` and ``screen`` are
installed in the system.
Legacy shell support
********************
When the legacy :ref:`Shell` subsystem is compiled with your application,
the native standard input (``stdin``) will be redirected to the shell.
You may use the shell interactively through the console,
by piping another process output to it, or by feeding it a file.
@ -580,16 +612,8 @@ These directives are:
Use example
===========
For example, you can build the shell sample app:
.. zephyr-app-commands::
:zephyr-app: samples/subsys/shell/shell_module
:host-os: unix
:board: native_posix
:goals: build
:compact:
And feed it the following set of commands through a pipe:
For example, after you have built an application with the legacy shell, you
can feed it the following set of commands through a pipe:
.. code-block:: console

View file

@ -2,5 +2,4 @@ CONFIG_ARCH_POSIX=y
CONFIG_SOC_POSIX=y
CONFIG_BOARD_NATIVE_POSIX=y
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=1000000

View file

@ -255,7 +255,6 @@ config NATIVE_POSIX_CONSOLE
bool "Use the host terminal for console"
depends on ARCH_POSIX
select CONSOLE_HAS_DRIVER
default y
help
Use the host terminal (where the native_posix binary was launched) for the
Zephyr console
@ -263,7 +262,7 @@ config NATIVE_POSIX_CONSOLE
config NATIVE_POSIX_STDIN_CONSOLE
bool "Use the host terminal stdin"
depends on NATIVE_POSIX_CONSOLE
default y
default y if !NATIVE_UART_0_ON_STDINOUT
help
Feed the host terminal stdin to the Zephyr console/shell.

View file

@ -26,3 +26,8 @@ zephyr_library_sources_if_kconfig(usart_mcux_lpc.c)
zephyr_library_sources_if_kconfig(uart_psoc6.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE uart_handlers.c)
if(CONFIG_UART_NATIVE_POSIX)
zephyr_library_compile_definitions(NO_POSIX_CHEATS)
zephyr_library_sources(uart_native_posix.c)
endif()

View file

@ -71,6 +71,8 @@ source "drivers/serial/Kconfig.imx"
source "drivers/serial/Kconfig.stellaris"
source "drivers/serial/Kconfig.native_posix"
source "drivers/serial/Kconfig.nsim"
source "drivers/serial/Kconfig.usart_sam"

View file

@ -0,0 +1,59 @@
config UART_NATIVE_POSIX
bool "UART driver for native_posix"
select SERIAL_HAS_DRIVER
depends on ARCH_POSIX
help
This enables a UART driver for the POSIX ARCH. The driver can be configured
to either connect to the terminal from which native_posix was run, or into
one decicated pseudoterminal for this UART.
if UART_NATIVE_POSIX
config UART_NATIVE_POSIX_PORT_0_NAME
string "Port 0 Device Name"
default "UART_0"
depends on UART_NATIVE_POSIX
help
This is the device name for UART, and is included in the device
struct.
choice
prompt "Native UART Port 0 connection"
default NATIVE_UART_0_ON_OWN_PTY
config NATIVE_UART_0_ON_OWN_PTY
bool "Connect the UART to its own pseudo terminal"
help
Connect the UART to its own pseudoterminal. This is the preferred option
for users who want to use Zephyr's shell.
Moreover this option does not conflict with any other native_posix backend
which may use the calling shell standard input/output.
config NATIVE_UART_0_ON_STDINOUT
bool "Connect the UART to the invoking shell stdin/stdout"
help
Connect the UART to the stdin & stdout of the calling shell/terminal which
invoked the native_posix executable. This is good enough for automated
testing, or when feeding from a file/pipe.
Note that other, non UART messages, will also be printed to the terminal.
This option should NOT be used in conjunction with the legacy shell
native_posix backend (NATIVE_POSIX_STDIN_CONSOLE)
It is strongly discouraged to try to use this option with the new shell
interactively, as the default terminal configuration is NOT appropriate
for interactive use.
endchoice
config NATIVE_UART_AUTOATTACH_DEFAULT_CMD
string "Default command to attach the UART to a new terminal"
default "xterm -e screen %s &"
help
If the native_posix executable is called with the --attach_uart command line
option, this will be the default command which will be run to attach a new
terminal to it.
Note that this command must have one, and only one, '%s' as placeholder for
the pseudoterminal device name (e.g. /dev/pts/35)
This is only applicable if a UART is configured to use its own PTY with
NATIVE_UART_x_ON_OWN_PTY.
endif #UART_NATIVE_POSIX

View file

@ -0,0 +1,335 @@
/*
* Copyright (c) 2018, Oticon A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pty.h>
#include <fcntl.h>
#include <sys/select.h>
#include <unistd.h>
#include "uart.h"
#include "cmdline.h" /* native_posix command line options header */
#include "soc.h"
/*
* UART driver for POSIX ARCH based boards.
* It can either be connected to the process STDIN+STDOUT
* OR
* to a dedicated pseudo terminal
*
* The 2nd option is the recommended one for interactive use, as the pseudo
* terminal driver will be configured in "raw" mode, and will therefore behave
* more like a real UART.
*
* When connected to its own pseudo terminal, it may also auto attach a terminal
* emulator to it, if set so from command line.
*/
static int np_uart_stdin_poll_in(struct device *dev, unsigned char *p_char);
static int np_uart_tty_poll_in(struct device *dev, unsigned char *p_char);
static unsigned char np_uart_poll_out(struct device *dev,
unsigned char out_char);
static bool auto_attach;
static const char default_cmd[] = CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD;
static char *auto_attach_cmd;
static struct uart_driver_api np_uart_driver_api = {
.poll_out = np_uart_poll_out,
.poll_in = np_uart_tty_poll_in,
};
struct native_uart_status {
int out_fd; /* File descriptor used for output */
int in_fd; /* File descriptor used for input */
};
#define ERROR posix_print_error_and_exit
#define WARN posix_print_warning
/**
* Attempt to connect a terminal emulator to the slave side of the pty
* If -attach_uart_cmd=<cmd> is provided as a command line option, <cmd> will be
* used. Otherwise, the default command,
* CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD, will be used instead
*/
static void attach_to_tty(const char *slave_tty)
{
if (auto_attach_cmd == NULL) {
auto_attach_cmd = (char *)default_cmd;
}
char command[strlen(auto_attach_cmd) + strlen(slave_tty)];
sprintf(command, auto_attach_cmd, slave_tty);
int ret = system(command);
if (ret != 0) {
WARN("Could not attach to the UART with \"%s\"\n", command);
WARN("The command returned %i\n", WEXITSTATUS(ret));
}
}
/**
* Attempt to allocate and open a new pseudoterminal
*
* Returns the file descriptor of the master side
* If auto_attach was set, it will also attempt to connect a new terminal
* emulator to its slave side.
*/
static int open_tty(void)
{
int master_pty;
char *slave_pty_name;
struct termios ter;
struct winsize win;
int err_nbr;
int ret;
win.ws_col = 80;
win.ws_row = 24;
master_pty = posix_openpt(O_RDWR | O_NOCTTY);
if (master_pty == -1) {
ERROR("Could not open a new TTY for the UART\n");
}
ret = grantpt(master_pty);
if (ret == -1) {
err_nbr = errno;
close(master_pty);
ERROR("Could not grant access to the slave PTY side (%i)\n",
errno);
}
ret = unlockpt(master_pty);
if (ret == -1) {
err_nbr = errno;
close(master_pty);
ERROR("Could not unlock the slave PTY side (%i)\n", errno);
}
slave_pty_name = ptsname(master_pty);
if (slave_pty_name == NULL) {
err_nbr = errno;
close(master_pty);
ERROR("Error getting slave PTY device name (%i)\n", errno);
}
/* Set the master PTY as non blocking */
fcntl(master_pty, F_SETFL, fcntl(master_pty, F_GETFL) | O_NONBLOCK);
/*
* Set terminal in "raw" mode:
* Not canonical (no line input)
* No signal generation from Ctr+{C|Z..}
* No echoing, no input or output processing
* No replacing of NL or CR
* No flow control
*/
ret = tcgetattr(master_pty, &ter);
if (ret == -1) {
ERROR("Could not read terminal driver settings\n");
}
ter.c_cc[VMIN] = 0;
ter.c_cc[VTIME] = 0;
ter.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
ter.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK
| ISTRIP | IXON | PARMRK);
ter.c_oflag &= ~OPOST;
ret = tcsetattr(master_pty, TCSANOW, &ter);
if (ret == -1) {
ERROR("Could not change terminal driver settings\n");
}
posix_print_trace("UART connected to pseudotty: %s\n", slave_pty_name);
if (auto_attach) {
attach_to_tty(slave_pty_name);
}
return master_pty;
}
/**
* @brief Initialize the native_posix serial port
*
* @param dev UART device struct
*
* @return 0 (if it fails catastrophically, the execution is terminated)
*/
static int np_uart_init(struct device *dev)
{
struct native_uart_status *d;
d = (struct native_uart_status *)dev->driver_data;
if (IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
int tty_fn = open_tty();
d->in_fd = tty_fn;
d->out_fd = tty_fn;
np_uart_driver_api.poll_in = np_uart_tty_poll_in;
} else { /* NATIVE_UART_0_ON_STDINOUT */
d->in_fd = STDIN_FILENO;
d->out_fd = STDOUT_FILENO;
np_uart_driver_api.poll_in = np_uart_stdin_poll_in;
if (isatty(STDIN_FILENO)) {
WARN("The UART dirver has been configured to map to the"
" process stdin&out (NATIVE_UART_0_ON_STDINOUT), "
"but stdin seems to be left attached to the shell."
" This will most likely NOT behave as you want it "
"to. This option is NOT meant for interactive use "
"but for piping/feeding from/to files to the UART"
);
}
}
dev->driver_api = &np_uart_driver_api;
return 0;
}
/*
* @brief Output a character towards the serial port
*
* @param dev UART device struct
* @param out_char Character to send.
*/
static unsigned char np_uart_poll_out(struct device *dev,
unsigned char out_char)
{
int ret;
struct native_uart_status *d;
d = (struct native_uart_status *)dev->driver_data;
ret = write(d->out_fd, &out_char, 1);
if (ret != 1) {
WARN("%s: a character could not be output\n", __func__);
}
return out_char;
}
/**
* @brief Poll the device for input.
*
* @param dev UART device structure.
* @param p_char Pointer to character.
*
* @retval 0 If a character arrived and was stored in p_char
* @retval -1 If no character was available to read
*/
static int np_uart_stdin_poll_in(struct device *dev, unsigned char *p_char)
{
static bool disconnected;
if (disconnected || feof(stdin)) {
/*
* The stdinput is fed from a file which finished or the user
* pressed Crtl+D
*/
disconnected = true;
return -1;
}
int n = -1;
int in_f = ((struct native_uart_status *)dev->driver_data)->in_fd;
int ready;
fd_set readfds;
static struct timeval timeout; /* just zero */
FD_ZERO(&readfds);
FD_SET(in_f, &readfds);
ready = select(in_f+1, &readfds, NULL, NULL, &timeout);
if (ready == 0) {
return -1;
} else if (ready == -1) {
ERROR("%s: Error on select ()\n", __func__);
}
n = read(in_f, p_char, 1);
if (n == -1) {
return -1;
}
return 0;
}
/**
* @brief Poll the device for input.
*
* @param dev UART device structure.
* @param p_char Pointer to character.
*
* @retval 0 If a character arrived and was stored in p_char
* @retval -1 If no character was available to read
*/
static int np_uart_tty_poll_in(struct device *dev, unsigned char *p_char)
{
int n = -1;
int in_f = ((struct native_uart_status *)dev->driver_data)->in_fd;
n = read(in_f, p_char, 1);
if (n == -1) {
return -1;
}
return 0;
}
static struct native_uart_status native_uart_status_0;
DEVICE_INIT(uart_native_posix0,
CONFIG_UART_NATIVE_POSIX_PORT_0_NAME, &np_uart_init,
(void *)&native_uart_status_0, NULL,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
static void np_add_uart_options(void)
{
if (!IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
return;
}
static struct args_struct_t uart_options[] = {
/*
* Fields:
* manual, mandatory, switch,
* option_name, var_name ,type,
* destination, callback,
* description
*/
{false, false, true,
"attach_uart", "", 'b',
(void *)&auto_attach, NULL,
"Automatically attach to the UART terminal"},
{false, false, false,
"attach_uart_cmd", "\"cmd\"", 's',
(void *)&auto_attach_cmd, NULL,
"Command used to automatically attach to the terminal, by "
"default: '" CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD "'"},
ARG_TABLE_ENDMARKER
};
native_add_command_line_opts(uart_options);
}
static void np_cleanup_uart(void)
{
if (IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
if (native_uart_status_0.in_fd != 0) {
close(native_uart_status_0.in_fd);
}
}
}
NATIVE_TASK(np_add_uart_options, PRE_BOOT_1, 11);
NATIVE_TASK(np_cleanup_uart, ON_EXIT, 99);