drivers: serial: native tty: Split in top and bottom

Split the native tty serial driver in a top and bottom to enable using it
with embedded libCs.

Signed-off-by: Marko Sagadin <marko.sagadin42@gmail.com>
This commit is contained in:
Marko Sagadin 2023-07-11 22:33:12 +02:00 committed by Carles Cufí
commit 3d5e660903
5 changed files with 387 additions and 230 deletions

View file

@ -80,6 +80,11 @@ endif()
if(CONFIG_UART_NATIVE_TTY)
zephyr_library_compile_definitions(NO_POSIX_CHEATS)
zephyr_library_sources(uart_native_tty.c)
if (CONFIG_NATIVE_APPLICATION)
zephyr_library_sources(uart_native_tty_bottom.c)
else()
target_sources(native_simulator INTERFACE uart_native_tty_bottom.c)
endif()
endif()
zephyr_library_sources_ifdef(CONFIG_SERIAL_TEST serial_test.c)

View file

@ -5,7 +5,7 @@
* /dev/ttyACM0). Only polling Uart API is implemented. Driver can be configured via devicetree,
* command line options or at runtime.
*
* To learn more see Native TYY section at:
* To learn more see Native TTY section at:
* https://docs.zephyrproject.org/latest/boards/posix/native_posix/doc/index.html
* or
* ${ZEPHYR_BASE}/boards/posix/native_posix/doc/index.rst
@ -18,18 +18,15 @@
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <nsi_tracing.h>
#include "cmdline.h"
#include "posix_native_task.h"
#include "uart_native_tty_bottom.h"
#include "nsi_host_trampolines.h"
#define WARN(msg, ...) posix_print_warning(msg, ##__VA_ARGS__)
#define ERROR(msg, ...) posix_print_error_and_exit(msg, ##__VA_ARGS__)
#define WARN(...) nsi_print_warning(__VA_ARGS__)
#define ERROR(...) nsi_print_error_and_exit(__VA_ARGS__)
#define DT_DRV_COMPAT zephyr_native_tty_uart
@ -48,170 +45,68 @@ struct native_tty_config {
struct uart_config uart_config;
};
struct baudrate_termios_pair {
int baudrate;
speed_t termios_baudrate;
};
/**
* @brief Lookup table for mapping the baud rate to the macro understood by termios.
*/
static const struct baudrate_termios_pair baudrate_lut[] = {
{1200, B1200}, {1800, B1800}, {2400, B2400}, {4800, B4800},
{9600, B9600}, {19200, B19200}, {38400, B38400}, {57600, B57600},
{115200, B115200}, {230400, B230400}, {460800, B460800}, {500000, B500000},
{576000, B576000}, {921600, B921600}, {1000000, B1000000}, {1152000, B1152000},
{1500000, B1500000}, {2000000, B2000000}, {2500000, B2500000}, {3000000, B3000000},
{3500000, B3500000}, {4000000, B4000000},
};
/**
* @brief Set given termios to defaults appropriate for communicating with serial port devices.
* @brief Convert from uart_config to native_tty_bottom_cfg eqvivalent struct
*
* @param ter
* @param bottom_cfg
* @param cfg
*
* @return 0 on success, negative errno otherwise.
*/
static inline void native_tty_termios_defaults_set(struct termios *ter)
static int native_tty_conv_to_bottom_cfg(struct native_tty_bottom_cfg *bottom_cfg,
const struct uart_config *cfg)
{
/* Set terminal in "serial" mode:
* - Not canonical (no line input)
* - No signal generation from Ctr+{C|Z..}
* - No echoing
*/
ter->c_lflag &= ~(ICANON | ISIG | ECHO);
bottom_cfg->baudrate = cfg->baudrate;
/* No special interpretation of output bytes.
* No conversion of newline to carriage return/line feed.
*/
ter->c_oflag &= ~(OPOST | ONLCR);
/* No software flow control. */
ter->c_iflag &= ~(IXON | IXOFF | IXANY);
/* No blocking, return immediately with what is available. */
ter->c_cc[VMIN] = 0;
ter->c_cc[VTIME] = 0;
/* No special handling of bytes on receive. */
ter->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
/* - Enable reading data and ignore control lines */
ter->c_cflag |= CREAD | CLOCAL;
}
/**
* @brief Set the baud rate speed in the termios structure
*
* @param ter
* @param baudrate
*
* @retval 0 If successful,
* @retval -ENOTSUP If requested baud rate is not supported.
*/
static inline int native_tty_baud_speed_set(struct termios *ter, int baudrate)
{
for (int i = 0; i < ARRAY_SIZE(baudrate_lut); i++) {
if (baudrate_lut[i].baudrate == baudrate) {
cfsetospeed(ter, baudrate_lut[i].termios_baudrate);
cfsetispeed(ter, baudrate_lut[i].termios_baudrate);
return 0;
}
}
return -ENOTSUP;
}
/**
* @brief Set parity setting in the termios structure
*
* @param ter
* @param parity
*
* @retval 0 If successful.
* @retval -ENOTSUP If requested parity is not supported.
*/
static inline int native_tty_baud_parity_set(struct termios *ter, enum uart_config_parity parity)
{
switch (parity) {
switch (cfg->parity) {
case UART_CFG_PARITY_NONE:
ter->c_cflag &= ~PARENB;
bottom_cfg->parity = NTB_PARITY_NONE;
break;
case UART_CFG_PARITY_ODD:
ter->c_cflag |= PARENB;
ter->c_cflag |= PARODD;
bottom_cfg->parity = NTB_PARITY_ODD;
break;
case UART_CFG_PARITY_EVEN:
ter->c_cflag |= PARENB;
ter->c_cflag &= ~PARODD;
bottom_cfg->parity = NTB_PARITY_EVEN;
break;
default:
/* Parity options mark and space (UART_CFG_PARITY_MARK and UART_CFG_PARITY_SPACE)
* are not supported on this driver.
*/
return -ENOTSUP;
}
return 0;
}
/**
* @brief Set the number of stop bits in the termios structure
*
* @param ter
* @param stop_bits
*
* @retval 0 If successful.
* @retval -ENOTSUP If requested number of stop bits is not supported.
*/
static inline int native_tty_stop_bits_set(struct termios *ter,
enum uart_config_stop_bits stop_bits)
{
switch (stop_bits) {
switch (cfg->data_bits) {
case UART_CFG_STOP_BITS_1:
ter->c_cflag &= ~CSTOPB;
bottom_cfg->stop_bits = NTB_STOP_BITS_1;
break;
case UART_CFG_STOP_BITS_2:
ter->c_cflag |= CSTOPB;
bottom_cfg->stop_bits = NTB_STOP_BITS_2;
break;
default:
/* Anything else is not supported in termios. */
return -ENOTSUP;
}
return 0;
}
/**
* @brief Set the number of data bits in the termios structure
*
* @param ter
* @param stop_bits
*
* @retval 0 If successful.
* @retval -ENOTSUP If requested number of data bits is not supported.
*/
static inline int native_tty_data_bits_set(struct termios *ter,
enum uart_config_data_bits data_bits)
{
unsigned int data_bits_to_set;
switch (data_bits) {
switch (cfg->data_bits) {
case UART_CFG_DATA_BITS_5:
data_bits_to_set = CS5;
bottom_cfg->data_bits = NTB_DATA_BITS_5;
break;
case UART_CFG_DATA_BITS_6:
data_bits_to_set = CS6;
bottom_cfg->data_bits = NTB_DATA_BITS_6;
break;
case UART_CFG_DATA_BITS_7:
data_bits_to_set = CS7;
bottom_cfg->data_bits = NTB_DATA_BITS_7;
break;
case UART_CFG_DATA_BITS_8:
data_bits_to_set = CS8;
bottom_cfg->data_bits = NTB_DATA_BITS_8;
break;
default:
/* Anything else is not supported in termios */
return -ENOTSUP;
}
/* Clear all bits that set the data size */
ter->c_cflag &= ~CSIZE;
ter->c_cflag |= data_bits_to_set;
if (cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) {
WARN("Could not set flow control, any kind of hw flow control is not supported.\n");
return -ENOTSUP;
}
bottom_cfg->flow_ctrl = NTB_FLOW_CTRL_NONE;
return 0;
}
@ -225,10 +120,10 @@ static void native_tty_uart_poll_out(const struct device *dev, unsigned char out
{
struct native_tty_data *data = dev->data;
int ret = write(data->fd, &out_char, 1);
int ret = nsi_host_write(data->fd, &out_char, 1);
if (ret == -1) {
ERROR("Could not write to %s, reason: %s\n", data->serial_port, strerror(errno));
ERROR("Could not write to %s\n", data->serial_port);
}
}
@ -245,91 +140,21 @@ static int native_tty_uart_poll_in(const struct device *dev, unsigned char *p_ch
{
struct native_tty_data *data = dev->data;
return read(data->fd, p_char, 1) > 0 ? 0 : -1;
return nsi_host_read(data->fd, p_char, 1) > 0 ? 0 : -1;
}
static int native_tty_configure(const struct device *dev, const struct uart_config *cfg)
{
int rc, err;
int fd = ((struct native_tty_data *)dev->data)->fd;
struct native_tty_bottom_cfg bottom_cfg;
/* Structure used to control properties of a serial port */
struct termios ter;
/* Read current terminal driver settings */
rc = tcgetattr(fd, &ter);
int rc = native_tty_conv_to_bottom_cfg(&bottom_cfg, cfg);
if (rc) {
WARN("Could not read terminal driver settings\n");
WARN("Could not convert uart config to native tty bottom cfg\n");
return rc;
}
native_tty_termios_defaults_set(&ter);
rc = native_tty_baud_speed_set(&ter, cfg->baudrate);
if (rc) {
WARN("Could not set baudrate, as %d is not supported.\n", cfg->baudrate);
return rc;
}
rc = native_tty_baud_parity_set(&ter, cfg->parity);
if (rc) {
WARN("Could not set parity.\n");
return rc;
}
rc = native_tty_stop_bits_set(&ter, cfg->stop_bits);
if (rc) {
WARN("Could not set number of data bits.\n");
return rc;
}
rc = native_tty_data_bits_set(&ter, cfg->data_bits);
if (rc) {
WARN("Could not set number of data bits.\n");
return rc;
}
if (cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) {
WARN("Could not set flow control, any kind of hw flow control is not supported.\n");
return -ENOTSUP;
}
rc = tcsetattr(fd, TCSANOW, &ter);
if (rc) {
err = errno;
WARN("Could not set serial port settings, reason: %s\n", strerror(err));
return err;
}
/* tcsetattr returns success if ANY of the requested changes were successfully carried out,
* not if ALL were. So we need to read back the settings and check if they are equal to the
* requested ones.
*/
struct termios read_ter;
rc = tcgetattr(fd, &read_ter);
if (rc) {
err = errno;
WARN("Could not read serial port settings, reason: %s\n", strerror(err));
return err;
}
if (ter.c_cflag != read_ter.c_cflag || ter.c_iflag != read_ter.c_iflag ||
ter.c_oflag != read_ter.c_oflag || ter.c_lflag != read_ter.c_lflag ||
ter.c_line != read_ter.c_line || ter.c_ispeed != read_ter.c_ispeed ||
ter.c_ospeed != read_ter.c_ospeed || 0 != memcmp(ter.c_cc, read_ter.c_cc, NCCS)) {
WARN("Read serial port settings do not match set ones.\n");
return -EBADE;
}
/* Flush both input and output */
rc = tcflush(fd, TCIOFLUSH);
if (rc) {
WARN("Could not flush serial port\n");
return rc;
}
return 0;
return native_tty_configure_bottom(fd, &bottom_cfg);
}
static int native_tty_serial_init(const struct device *dev)
@ -337,22 +162,24 @@ static int native_tty_serial_init(const struct device *dev)
struct native_tty_data *data = dev->data;
struct uart_config uart_config = ((struct native_tty_config *)dev->config)->uart_config;
/* Default value for cmd_serial_port is NULL, this is due to the set 's' type in command
* line opts. If it is anything else then it was configured via command line.
/* Default value for cmd_serial_port is NULL, this is due to the set 's' type in
* command line opts. If it is anything else then it was configured via command
* line.
*/
if (data->cmd_serial_port) {
data->serial_port = data->cmd_serial_port;
}
/* Default value for cmd_baudrate is UINT32_MAX, this is due to the set 'u' type in command
* line opts. If it is anything else then it was configured via command line.
/* Default value for cmd_baudrate is UINT32_MAX, this is due to the set 'u' type in
* command line opts. If it is anything else then it was configured via command
* line.
*/
if (data->cmd_baudrate != UINT32_MAX) {
uart_config.baudrate = data->cmd_baudrate;
}
/* Serial port needs to be set either in the devicetree or provided via command line opts,
* if that is not the case, then abort.
/* Serial port needs to be set either in the devicetree or provided via command line
* opts, if that is not the case, then abort.
*/
if (!data->serial_port) {
ERROR("%s: path to the serial port was not set.\n", dev->name);
@ -361,11 +188,8 @@ static int native_tty_serial_init(const struct device *dev)
/* Try to open a serial port as with read/write access, also prevent serial port
* from becoming the controlling terminal.
*/
data->fd = open(data->serial_port, O_RDWR | O_NOCTTY);
if (data->fd < 0) {
ERROR("%s: failed to open serial port %s, reason: %s\n", dev->name,
data->serial_port, strerror(errno));
}
data->fd = native_tty_open_tty_bottom(data->serial_port);
if (native_tty_configure(dev, &uart_config)) {
ERROR("%s: could not configure serial port %s\n", dev->name, data->serial_port);
@ -408,7 +232,6 @@ DT_INST_FOREACH_STATUS_OKAY(NATIVE_TTY_INSTANCE);
#define INST_NAME(inst) DEVICE_DT_NAME(DT_DRV_INST(inst))
#define NATIVE_TTY_COMMAND_LINE_OPTS(inst) \
{ \
.option = INST_NAME(inst) "_port", \
@ -429,7 +252,8 @@ DT_INST_FOREACH_STATUS_OKAY(NATIVE_TTY_INSTANCE);
},
/**
* @brief Adds command line options for setting serial port and baud rate for each uart device.
* @brief Adds command line options for setting serial port and baud rate for each uart
* device.
*/
static void native_tty_add_serial_options(void)
{
@ -441,7 +265,7 @@ static void native_tty_add_serial_options(void)
#define NATIVE_TTY_CLEANUP(inst) \
if (native_tty_##inst##_data.fd != 0) { \
close(native_tty_##inst##_data.fd); \
nsi_host_close(native_tty_##inst##_data.fd); \
}
/**

View file

@ -0,0 +1,246 @@
/**
* @brief "Bottom" of native tty uart driver
*
* Copyright (c) 2023 Marko Sagadin
* SPDX-License-Identifier: Apache-2.0
*/
#include "uart_native_tty_bottom.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <nsi_tracing.h>
#define WARN(...) nsi_print_warning(__VA_ARGS__)
#define ERROR(...) nsi_print_error_and_exit(__VA_ARGS__)
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
struct baudrate_termios_pair {
int baudrate;
speed_t termios_baudrate;
};
/**
* @brief Lookup table for mapping the baud rate to the macro understood by termios.
*/
static const struct baudrate_termios_pair baudrate_lut[] = {
{1200, B1200}, {1800, B1800}, {2400, B2400}, {4800, B4800},
{9600, B9600}, {19200, B19200}, {38400, B38400}, {57600, B57600},
{115200, B115200}, {230400, B230400}, {460800, B460800}, {500000, B500000},
{576000, B576000}, {921600, B921600}, {1000000, B1000000}, {1152000, B1152000},
{1500000, B1500000}, {2000000, B2000000}, {2500000, B2500000}, {3000000, B3000000},
{3500000, B3500000}, {4000000, B4000000},
};
/**
* @brief Set given termios to defaults appropriate for communicating with serial port devices.
*
* @param ter
*/
static inline void native_tty_termios_defaults_set(struct termios *ter)
{
/* Set terminal in "serial" mode:
* - Not canonical (no line input)
* - No signal generation from Ctr+{C|Z..}
* - No echoing
*/
ter->c_lflag &= ~(ICANON | ISIG | ECHO);
/* No special interpretation of output bytes.
* No conversion of newline to carriage return/line feed.
*/
ter->c_oflag &= ~(OPOST | ONLCR);
/* No software flow control. */
ter->c_iflag &= ~(IXON | IXOFF | IXANY);
/* No blocking, return immediately with what is available. */
ter->c_cc[VMIN] = 0;
ter->c_cc[VTIME] = 0;
/* No special handling of bytes on receive. */
ter->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
/* - Enable reading data and ignore control lines */
ter->c_cflag |= CREAD | CLOCAL;
}
/**
* @brief Set the baud rate speed in the termios structure
*
* @param ter
* @param baudrate
*/
static inline void native_tty_baud_speed_set(struct termios *ter, int baudrate)
{
for (int i = 0; i < ARRAY_SIZE(baudrate_lut); i++) {
if (baudrate_lut[i].baudrate == baudrate) {
cfsetospeed(ter, baudrate_lut[i].termios_baudrate);
cfsetispeed(ter, baudrate_lut[i].termios_baudrate);
return;
}
}
ERROR("Could not set baudrate, as %d is not supported.\n", baudrate);
}
/**
* @brief Set parity setting in the termios structure
*
* @param ter
* @param parity
*/
static inline void native_tty_baud_parity_set(struct termios *ter,
enum native_tty_bottom_parity parity)
{
switch (parity) {
case NTB_PARITY_NONE:
ter->c_cflag &= ~PARENB;
break;
case NTB_PARITY_ODD:
ter->c_cflag |= PARENB;
ter->c_cflag |= PARODD;
break;
case NTB_PARITY_EVEN:
ter->c_cflag |= PARENB;
ter->c_cflag &= ~PARODD;
break;
default:
/* Parity options mark and space are not supported on this driver. */
ERROR("Could not set parity.\n");
}
}
/**
* @brief Set the number of stop bits in the termios structure
*
* @param ter
* @param stop_bits
*
*/
static inline void native_tty_stop_bits_set(struct termios *ter,
enum native_tty_bottom_stop_bits stop_bits)
{
switch (stop_bits) {
case NTB_STOP_BITS_1:
ter->c_cflag &= ~CSTOPB;
break;
case NTB_STOP_BITS_2:
ter->c_cflag |= CSTOPB;
break;
default:
/* Anything else is not supported in termios. */
ERROR("Could not set number of data bits.\n");
}
}
/**
* @brief Set the number of data bits in the termios structure
*
* @param ter
* @param stop_bits
*
*/
static inline void native_tty_data_bits_set(struct termios *ter,
enum native_tty_bottom_data_bits data_bits)
{
unsigned int data_bits_to_set = CS5;
switch (data_bits) {
case NTB_DATA_BITS_5:
data_bits_to_set = CS5;
break;
case NTB_DATA_BITS_6:
data_bits_to_set = CS6;
break;
case NTB_DATA_BITS_7:
data_bits_to_set = CS7;
break;
case NTB_DATA_BITS_8:
data_bits_to_set = CS8;
break;
default:
/* Anything else is not supported in termios */
ERROR("Could not set number of data bits.\n");
}
/* Clear all bits that set the data size */
ter->c_cflag &= ~CSIZE;
ter->c_cflag |= data_bits_to_set;
}
int native_tty_open_tty_bottom(const char *pathname)
{
int fd = open(pathname, O_RDWR | O_NOCTTY);
if (fd < 0) {
ERROR("Failed to open serial port %s, errno: %i\n", pathname, errno);
}
return fd;
}
int native_tty_configure_bottom(int fd, struct native_tty_bottom_cfg *cfg)
{
int rc, err;
/* Structure used to control properties of a serial port */
struct termios ter;
/* Read current terminal driver settings */
rc = tcgetattr(fd, &ter);
if (rc) {
WARN("Could not read terminal driver settings\n");
return rc;
}
native_tty_termios_defaults_set(&ter);
native_tty_baud_speed_set(&ter, cfg->baudrate);
native_tty_baud_parity_set(&ter, cfg->parity);
native_tty_stop_bits_set(&ter, cfg->stop_bits);
native_tty_data_bits_set(&ter, cfg->data_bits);
cfg->flow_ctrl = NTB_FLOW_CTRL_NONE;
rc = tcsetattr(fd, TCSANOW, &ter);
if (rc) {
err = errno;
WARN("Could not set serial port settings, reason: %s\n", strerror(err));
return err;
}
/* tcsetattr returns success if ANY of the requested changes were successfully carried out,
* not if ALL were. So we need to read back the settings and check if they are equal to the
* requested ones.
*/
struct termios read_ter;
rc = tcgetattr(fd, &read_ter);
if (rc) {
err = errno;
WARN("Could not read serial port settings, reason: %s\n", strerror(err));
return err;
}
if (ter.c_cflag != read_ter.c_cflag || ter.c_iflag != read_ter.c_iflag ||
ter.c_oflag != read_ter.c_oflag || ter.c_lflag != read_ter.c_lflag ||
ter.c_line != read_ter.c_line || ter.c_ispeed != read_ter.c_ispeed ||
ter.c_ospeed != read_ter.c_ospeed || 0 != memcmp(ter.c_cc, read_ter.c_cc, NCCS)) {
WARN("Read serial port settings do not match set ones.\n");
return -1;
}
/* Flush both input and output */
rc = tcflush(fd, TCIOFLUSH);
if (rc) {
WARN("Could not flush serial port\n");
return rc;
}
return 0;
}

View file

@ -0,0 +1,82 @@
/**
* @brief "Bottom" of native tty uart driver
*
* When built with the native_simulator this will be built in the runner context,
* that is, with the host C library, and with the host include paths.
*
* Copyright (c) 2023 Marko Sagadin
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef DRIVERS_SERIAL_UART_NATIVE_TTY_BOTTOM_H
#define DRIVERS_SERIAL_UART_NATIVE_TTY_BOTTOM_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* Below enums are just differently namespaced copies of uart_config_* enums. Options that are not
* supported on the host are not listed.
*/
enum native_tty_bottom_parity {
NTB_PARITY_NONE,
NTB_PARITY_ODD,
NTB_PARITY_EVEN,
};
enum native_tty_bottom_stop_bits {
NTB_STOP_BITS_1,
NTB_STOP_BITS_2,
};
enum native_tty_bottom_data_bits {
NTB_DATA_BITS_5,
NTB_DATA_BITS_6,
NTB_DATA_BITS_7,
NTB_DATA_BITS_8,
};
enum native_tty_bottom_flow_control {
NTB_FLOW_CTRL_NONE,
};
struct native_tty_bottom_cfg {
uint32_t baudrate;
enum native_tty_bottom_parity parity;
enum native_tty_bottom_stop_bits stop_bits;
enum native_tty_bottom_data_bits data_bits;
enum native_tty_bottom_flow_control flow_ctrl;
};
/* Note: None of these functions are public interfaces. They are internal to the native tty driver.
*/
/**
* @brief Opens tty port on the given pathname
*
* Returned file descriptor can be then passed to native_tty_configure_bottom to configure it.
*
* @param pathname
*
* @return file descriptor
*/
int native_tty_open_tty_bottom(const char *pathname);
/**
* @brief Configure tty port
*
* @param fd File descriptor of the tty port.
* @param cfg Configuration struct.
*
* @retval 0 if successful,
* @retval -1 otherwise.
*/
int native_tty_configure_bottom(int fd, struct native_tty_bottom_cfg *cfg);
#ifdef __cplusplus
}
#endif
#endif /* DRIVERS_SERIAL_UART_NATIVE_TTY_BOTTOM_H */