From 53954358162990a4351ca902fbfbfd8ca93d9cd3 Mon Sep 17 00:00:00 2001 From: Roland Dobai Date: Tue, 14 Aug 2018 13:39:30 +0200 Subject: [PATCH] Add basic support for termios.h Closes https://github.com/espressif/esp-idf/issues/2063 --- .../newlib/platform_include/sys/termios.h | 296 +++++++++++ components/newlib/termios.c | 54 ++ components/vfs/Kconfig | 6 + components/vfs/include/esp_vfs.h | 33 ++ components/vfs/test/test_vfs_uart.c | 132 +++++ components/vfs/vfs.c | 100 ++++ components/vfs/vfs_uart.c | 473 +++++++++++++++++- 7 files changed, 1087 insertions(+), 7 deletions(-) create mode 100644 components/newlib/platform_include/sys/termios.h create mode 100644 components/newlib/termios.c diff --git a/components/newlib/platform_include/sys/termios.h b/components/newlib/platform_include/sys/termios.h new file mode 100644 index 0000000000..fd0eb5ca88 --- /dev/null +++ b/components/newlib/platform_include/sys/termios.h @@ -0,0 +1,296 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed 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. +// +// This header file is based on the termios header of +// "The Single UNIX (r) Specification, Version 2, Copyright (c) 1997 The Open Group". + +#ifndef __ESP_SYS_TERMIOS_H__ +#define __ESP_SYS_TERMIOS_H__ + +// ESP-IDF NOTE: This header provides only a compatibility layer for macros and functions defined in sys/termios.h. +// Not everything has a defined meaning for ESP-IDF (e.g. process leader IDs) and therefore are likely to be stubbed +// in actual implementations. + + +#include +#include +#include "sdkconfig.h" + +#ifdef CONFIG_SUPPORT_TERMIOS + +// subscripts for the array c_cc: +#define VEOF 0 /** EOF character */ +#define VEOL 1 /** EOL character */ +#define VERASE 2 /** ERASE character */ +#define VINTR 3 /** INTR character */ +#define VKILL 4 /** KILL character */ +#define VMIN 5 /** MIN value */ +#define VQUIT 6 /** QUIT character */ +#define VSTART 7 /** START character */ +#define VSTOP 8 /** STOP character */ +#define VSUSP 9 /** SUSP character */ +#define VTIME 10 /** TIME value */ +#define NCCS (VTIME + 1) /** Size of the array c_cc for control characters */ + +// input modes for use as flags in the c_iflag field +#define BRKINT (1u << 0) /** Signal interrupt on break. */ +#define ICRNL (1u << 1) /** Map CR to NL on input. */ +#define IGNBRK (1u << 2) /** Ignore break condition. */ +#define IGNCR (1u << 3) /** Ignore CR. */ +#define IGNPAR (1u << 4) /** Ignore characters with parity errors. */ +#define INLCR (1u << 5) /** Map NL to CR on input. */ +#define INPCK (1u << 6) /** Enable input parity check. */ +#define ISTRIP (1u << 7) /** Strip character. */ +#define IUCLC (1u << 8) /** Map upper-case to lower-case on input (LEGACY). */ +#define IXANY (1u << 9) /** Enable any character to restart output. */ +#define IXOFF (1u << 10) /** Enable start/stop input control. */ +#define IXON (1u << 11) /** Enable start/stop output control. */ +#define PARMRK (1u << 12) /** Mark parity errors. */ + +// output Modes for use as flags in the c_oflag field +#define OPOST (1u << 0) /** Post-process output */ +#define OLCUC (1u << 1) /** Map lower-case to upper-case on output (LEGACY). */ +#define ONLCR (1u << 2) /** Map NL to CR-NL on output. */ +#define OCRNL (1u << 3) /** Map CR to NL on output. */ +#define ONOCR (1u << 4) /** No CR output at column 0. */ +#define ONLRET (1u << 5) /** NL performs CR function. */ +#define OFILL (1u << 6) /** Use fill characters for delay. */ +#define NLDLY (1u << 7) /** Select newline delays: */ +#define NL0 (0u << 7) /** Newline character type 0. */ +#define NL1 (1u << 7) /** Newline character type 1. */ +#define CRDLY (3u << 8) /** Select carriage-return delays: */ +#define CR0 (0u << 8) /** Carriage-return delay type 0. */ +#define CR1 (1u << 8) /** Carriage-return delay type 1. */ +#define CR2 (2u << 8) /** Carriage-return delay type 2. */ +#define CR3 (3u << 8) /** Carriage-return delay type 3. */ +#define TABDLY (3u << 10) /** Select horizontal-tab delays: */ +#define TAB0 (0u << 10) /** Horizontal-tab delay type 0. */ +#define TAB1 (1u << 10) /** Horizontal-tab delay type 1. */ +#define TAB2 (2u << 10) /** Horizontal-tab delay type 2. */ +#define TAB3 (3u << 10) /** Expand tabs to spaces. */ +#define BSDLY (1u << 12) /** Select backspace delays: */ +#define BS0 (0u << 12) /** Backspace-delay type 0. */ +#define BS1 (1u << 12) /** Backspace-delay type 1. */ +#define VTDLY (1u << 13) /** Select vertical-tab delays: */ +#define VT0 (0u << 13) /** Vertical-tab delay type 0. */ +#define VT1 (1u << 13) /** Vertical-tab delay type 1. */ +#define FFDLY (1u << 14) /** Select form-feed delays: */ +#define FF0 (0u << 14) /** Form-feed delay type 0. */ +#define FF1 (1u << 14) /** Form-feed delay type 1. */ + +// Baud Rate Selection. Valid values for objects of type speed_t: +// CBAUD range B0 - B38400 +#define B0 0 /** Hang up */ +#define B50 1 +#define B75 2 +#define B110 3 +#define B134 4 +#define B150 5 +#define B200 6 +#define B300 7 +#define B600 8 +#define B1200 9 +#define B1800 10 +#define B2400 11 +#define B4800 12 +#define B9600 13 +#define B19200 14 +#define B38400 15 +// CBAUDEX range B57600 - B4000000 +#define B57600 16 +#define B115200 17 +#define B230400 18 +#define B460800 19 +#define B500000 20 +#define B576000 21 +#define B921600 22 +#define B1000000 23 +#define B1152000 24 +#define B1500000 25 +#define B2000000 26 +#define B2500000 27 +#define B3000000 28 +#define B3500000 29 +#define B4000000 30 + +// Control Modes for the c_cflag field: +#define CSIZE (3u << 0) /* Character size: */ +#define CS5 (0u << 0) /** 5 bits. */ +#define CS6 (1u << 0) /** 6 bits. */ +#define CS7 (2u << 0) /** 7 bits. */ +#define CS8 (3u << 0) /** 8 bits. */ +#define CSTOPB (1u << 2) /** Send two stop bits, else one. */ +#define CREAD (1u << 3) /** Enable receiver. */ +#define PARENB (1u << 4) /** Parity enable. */ +#define PARODD (1u << 5) /** Odd parity, else even. */ +#define HUPCL (1u << 6) /** Hang up on last close. */ +#define CLOCAL (1u << 7) /** Ignore modem status lines. */ +#define CBAUD (1u << 8) /** Use baud rates defined by B0-B38400 macros. */ +#define CBAUDEX (1u << 9) /** Use baud rates defined by B57600-B4000000 macros. */ +#define BOTHER (1u << 10) /** Use custom baud rates */ + +// Local Modes for c_lflag field: +#define ECHO (1u << 0) /** Enable echo. */ +#define ECHOE (1u << 1) /** Echo erase character as error-correcting backspace. */ +#define ECHOK (1u << 2) /** Echo KILL. */ +#define ECHONL (1u << 3) /** Echo NL. */ +#define ICANON (1u << 4) /** Canonical input (erase and kill processing). */ +#define IEXTEN (1u << 5) /** Enable extended input character processing. */ +#define ISIG (1u << 6) /** Enable signals. */ +#define NOFLSH (1u << 7) /** Disable flush after interrupt or quit. */ +#define TOSTOP (1u << 8) /** Send SIGTTOU for background output. */ +#define XCASE (1u << 9) /** Canonical upper/lower presentation (LEGACY). */ + +// Attribute Selection constants for use with tcsetattr(): +#define TCSANOW 0 /** Change attributes immediately. */ +#define TCSADRAIN 1 /** Change attributes when output has drained. */ +#define TCSAFLUSH 2 /** Change attributes when output has drained; also flush pending input. */ + +// Line Control constants for use with tcflush(): +#define TCIFLUSH 0 /** Flush pending input. Flush untransmitted output. */ +#define TCIOFLUSH 1 /** Flush both pending input and untransmitted output. */ +#define TCOFLUSH 2 /** Flush untransmitted output. */ + +// constants for use with tcflow(): +#define TCIOFF 0 /** Transmit a STOP character, intended to suspend input data. */ +#define TCION 1 /** Transmit a START character, intended to restart input data. */ +#define TCOOFF 2 /** Suspend output. */ +#define TCOON 3 /** Restart output. */ + +typedef uint8_t cc_t; +typedef uint32_t speed_t; +typedef uint16_t tcflag_t; + +struct termios +{ + tcflag_t c_iflag; /** Input modes */ + tcflag_t c_oflag; /** Output modes */ + tcflag_t c_cflag; /** Control modes */ + tcflag_t c_lflag; /** Local modes */ + cc_t c_cc[NCCS]; /** Control characters */ + speed_t c_ispeed; /** input baud rate */ + speed_t c_ospeed; /** output baud rate */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Extracts the input baud rate from the input structure exactly (without interpretation). + * + * @param p input termios structure + * @return input baud rate + */ +speed_t cfgetispeed(const struct termios *p); + +/** + * @brief Extracts the output baud rate from the input structure exactly (without interpretation). + * + * @param p input termios structure + * @return output baud rate + */ +speed_t cfgetospeed(const struct termios *p); + +/** + * @brief Set input baud rate in the termios structure + * + * There is no effect in hardware until a subsequent call of tcsetattr(). + * + * @param p input termios structure + * @param sp input baud rate + * @return 0 when successful, -1 otherwise with errno set + */ +int cfsetispeed(struct termios *p, speed_t sp); + +/** + * @brief Set output baud rate in the termios structure + * + * There is no effect in hardware until a subsequent call of tcsetattr(). + * + * @param p input termios structure + * @param sp output baud rate + * @return 0 when successful, -1 otherwise with errno set + */ +int cfsetospeed(struct termios *p, speed_t sp); + +/** + * @brief Wait for transmission of output + * + * @param fd file descriptor of the terminal + * @return 0 when successful, -1 otherwise with errno set + */ +int tcdrain(int fd); + +/** + * @brief Suspend or restart the transmission or reception of data + * + * @param fd file descriptor of the terminal + * @param action selects actions to do + * @return 0 when successful, -1 otherwise with errno set + */ +int tcflow(int fd, int action); + +/** + * @brief Flush non-transmitted output data and non-read input data + * + * @param fd file descriptor of the terminal + * @param select selects what should be flushed + * @return 0 when successful, -1 otherwise with errno set + */ +int tcflush(int fd, int select); + +/** + * @brief Gets the parameters of the terminal + * + * @param fd file descriptor of the terminal + * @param p output termios structure + * @return 0 when successful, -1 otherwise with errno set + */ +int tcgetattr(int fd, struct termios *p); + +/** + * @brief Get process group ID for session leader for controlling terminal + * + * @param fd file descriptor of the terminal + * @return process group ID when successful, -1 otherwise with errno set + */ +pid_t tcgetsid(int fd); + +/** + * @brief Send a break for a specific duration + * + * @param fd file descriptor of the terminal + * @param duration duration of break + * @return 0 when successful, -1 otherwise with errno set + */ +int tcsendbreak(int fd, int duration); + +/** + * @brief Sets the parameters of the terminal + * + * @param fd file descriptor of the terminal + * @param optional_actions optional actions + * @param p input termios structure + * @return 0 when successful, -1 otherwise with errno set + */ +int tcsetattr(int fd, int optional_actions, const struct termios *p); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // CONFIG_SUPPORT_TERMIOS + +#endif //__ESP_SYS_TERMIOS_H__ diff --git a/components/newlib/termios.c b/components/newlib/termios.c new file mode 100644 index 0000000000..bccd5bf839 --- /dev/null +++ b/components/newlib/termios.c @@ -0,0 +1,54 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed 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 "sdkconfig.h" + +#ifdef CONFIG_SUPPORT_TERMIOS + +#include +#include + +speed_t cfgetispeed(const struct termios *p) +{ + return p ? p->c_ispeed : B0; +} + +speed_t cfgetospeed(const struct termios *p) +{ + return p ? p->c_ospeed : B0; +} + +int cfsetispeed(struct termios *p, speed_t sp) +{ + if (p) { + p->c_ispeed = sp; + return 0; + } else { + errno = EINVAL; + return -1; + } +} + +int cfsetospeed(struct termios *p, speed_t sp) +{ + if (p) { + p->c_ospeed = sp; + return 0; + } else { + errno = EINVAL; + return -1; + } +} + +#endif // CONFIG_SUPPORT_TERMIOS diff --git a/components/vfs/Kconfig b/components/vfs/Kconfig index d3d4ae9ad2..19a565b2bc 100644 --- a/components/vfs/Kconfig +++ b/components/vfs/Kconfig @@ -9,4 +9,10 @@ config SUPPRESS_SELECT_DEBUG_OUTPUT It is possible to suppress these debug outputs by enabling this option. +config SUPPORT_TERMIOS + bool "Add support for termios.h" + default y + help + Disabling this option can save memory when the support for termios.h is not required. + endmenu diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 4d847274b4..d7467d227f 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -26,8 +26,10 @@ #include #include #include +#include #include #include +#include "sdkconfig.h" #ifdef __cplusplus extern "C" { @@ -178,6 +180,37 @@ typedef struct int (*truncate_p)(void* ctx, const char *path, off_t length); int (*truncate)(const char *path, off_t length); }; +#ifdef CONFIG_SUPPORT_TERMIOS + union { + int (*tcsetattr_p)(void *ctx, int fd, int optional_actions, const struct termios *p); + int (*tcsetattr)(int fd, int optional_actions, const struct termios *p); + }; + union { + int (*tcgetattr_p)(void *ctx, int fd, struct termios *p); + int (*tcgetattr)(int fd, struct termios *p); + }; + union { + int (*tcdrain_p)(void *ctx, int fd); + int (*tcdrain)(int fd); + }; + union { + int (*tcflush_p)(void *ctx, int fd, int select); + int (*tcflush)(int fd, int select); + }; + union { + int (*tcflow_p)(void *ctx, int fd, int action); + int (*tcflow)(int fd, int action); + }; + union { + pid_t (*tcgetsid_p)(void *ctx, int fd); + pid_t (*tcgetsid)(int fd); + }; + union { + int (*tcsendbreak_p)(void *ctx, int fd, int duration); + int (*tcsendbreak)(int fd, int duration); + }; +#endif // CONFIG_SUPPORT_TERMIOS + /** start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS */ esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, SemaphoreHandle_t *signal_sem); /** socket select function for socket FDs with the functionality of POSIX select(); this should be set only for the socket VFS */ diff --git a/components/vfs/test/test_vfs_uart.c b/components/vfs/test/test_vfs_uart.c index 20684cc2b4..2e45d76bf4 100644 --- a/components/vfs/test/test_vfs_uart.c +++ b/components/vfs/test/test_vfs_uart.c @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include "unity.h" #include "rom/uart.h" #include "soc/uart_struct.h" @@ -23,6 +26,7 @@ #include "freertos/semphr.h" #include "driver/uart.h" #include "esp_vfs_dev.h" +#include "esp_vfs.h" #include "sdkconfig.h" static void fwrite_str_loopback(const char* str, size_t size) @@ -198,3 +202,131 @@ TEST_CASE("can write to UART while another task is reading", "[vfs]") vSemaphoreDelete(read_arg.done); vSemaphoreDelete(write_arg.done); } + +#ifdef CONFIG_SUPPORT_TERMIOS +TEST_CASE("Can use termios for UART", "[vfs]") +{ + uart_config_t uart_config = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE + }; + uart_param_config(UART_NUM_1, &uart_config); + uart_driver_install(UART_NUM_1, 256, 256, 0, NULL, 0); + + const int uart_fd = open("/dev/uart/1", O_RDWR); + TEST_ASSERT_NOT_EQUAL_MESSAGE(uart_fd, -1, "Cannot open UART"); + esp_vfs_dev_uart_use_driver(1); + + TEST_ASSERT_EQUAL(-1, tcgetattr(uart_fd, NULL)); + TEST_ASSERT_EQUAL(EINVAL, errno); + + struct termios tios, tios_result; + + TEST_ASSERT_EQUAL(-1, tcgetattr(-1, &tios)); + TEST_ASSERT_EQUAL(EBADF, errno); + + TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios)); + + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSADRAIN, &tios)); + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSAFLUSH, &tios)); + + tios.c_iflag |= IGNCR; + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios)); + tios.c_iflag &= (~IGNCR); + TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result)); + TEST_ASSERT_EQUAL(IGNCR, tios_result.c_iflag & IGNCR); + memset(&tios_result, 0xFF, sizeof(struct termios)); + + tios.c_iflag |= ICRNL; + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios)); + tios.c_iflag &= (~ICRNL); + TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result)); + TEST_ASSERT_EQUAL(ICRNL, tios_result.c_iflag & ICRNL); + memset(&tios_result, 0xFF, sizeof(struct termios)); + + { + uart_word_length_t data_bit; + uart_stop_bits_t stop_bits; + uart_parity_t parity_mode; + + tios.c_cflag &= (~CSIZE); + tios.c_cflag &= (~CSTOPB); + tios.c_cflag &= (~PARENB); + tios.c_cflag |= CS6; + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios)); + tios.c_cflag &= (~CSIZE); + TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result)); + TEST_ASSERT_EQUAL(CS6, tios_result.c_cflag & CS6); + TEST_ASSERT_EQUAL(ESP_OK, uart_get_word_length(UART_NUM_1, &data_bit)); + TEST_ASSERT_EQUAL(UART_DATA_6_BITS, data_bit); + TEST_ASSERT_EQUAL(0, tios_result.c_cflag & CSTOPB); + TEST_ASSERT_EQUAL(ESP_OK, uart_get_stop_bits(UART_NUM_1, &stop_bits)); + TEST_ASSERT_EQUAL(UART_STOP_BITS_1, stop_bits); + TEST_ASSERT_EQUAL(ESP_OK, uart_get_parity(UART_NUM_1, &parity_mode)); + TEST_ASSERT_EQUAL(UART_PARITY_DISABLE, parity_mode); + memset(&tios_result, 0xFF, sizeof(struct termios)); + } + + { + uart_stop_bits_t stop_bits; + uart_parity_t parity_mode; + + tios.c_cflag |= CSTOPB; + tios.c_cflag |= (PARENB | PARODD); + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios)); + tios.c_cflag &= (~(CSTOPB | PARENB | PARODD)); + TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result)); + TEST_ASSERT_EQUAL(CSTOPB, tios_result.c_cflag & CSTOPB); + TEST_ASSERT_EQUAL(ESP_OK, uart_get_stop_bits(UART_NUM_1, &stop_bits)); + TEST_ASSERT_EQUAL(UART_STOP_BITS_2, stop_bits); + TEST_ASSERT_EQUAL(ESP_OK, uart_get_parity(UART_NUM_1, &parity_mode)); + TEST_ASSERT_EQUAL(UART_PARITY_ODD, parity_mode); + memset(&tios_result, 0xFF, sizeof(struct termios)); + } + + { + uint32_t baudrate; + + tios.c_cflag &= (~BOTHER); + tios.c_cflag |= CBAUD; + tios.c_ispeed = tios.c_ospeed = B38400; + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios)); + TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result)); + TEST_ASSERT_EQUAL(CBAUD, tios_result.c_cflag & CBAUD); + TEST_ASSERT_EQUAL(B38400, tios_result.c_ispeed); + TEST_ASSERT_EQUAL(B38400, tios_result.c_ospeed); + TEST_ASSERT_EQUAL(ESP_OK, uart_get_baudrate(UART_NUM_1, &baudrate)); + TEST_ASSERT_EQUAL(38400, baudrate); + + tios.c_cflag |= CBAUDEX; + tios.c_ispeed = tios.c_ospeed = B230400; + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios)); + TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result)); + TEST_ASSERT_EQUAL(BOTHER, tios_result.c_cflag & BOTHER); + // Setting the speed to 230400 will set it actually to 230423 + TEST_ASSERT_EQUAL(230423, tios_result.c_ispeed); + TEST_ASSERT_EQUAL(230423, tios_result.c_ospeed); + TEST_ASSERT_EQUAL(ESP_OK, uart_get_baudrate(UART_NUM_1, &baudrate)); + TEST_ASSERT_EQUAL(230423, baudrate); + + tios.c_cflag |= BOTHER; + tios.c_ispeed = tios.c_ospeed = 213; + TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios)); + TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result)); + TEST_ASSERT_EQUAL(BOTHER, tios_result.c_cflag & BOTHER); + TEST_ASSERT_EQUAL(213, tios_result.c_ispeed); + TEST_ASSERT_EQUAL(213, tios_result.c_ospeed); + TEST_ASSERT_EQUAL(ESP_OK, uart_get_baudrate(UART_NUM_1, &baudrate)); + TEST_ASSERT_EQUAL(213, baudrate); + + memset(&tios_result, 0xFF, sizeof(struct termios)); + } + + esp_vfs_dev_uart_use_nonblocking(1); + close(uart_fd); + uart_driver_delete(UART_NUM_1); +} +#endif // CONFIG_SUPPORT_TERMIOS diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index f0a195992d..e8b99eff2c 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -994,3 +994,103 @@ void esp_vfs_select_triggered_isr(SemaphoreHandle_t *signal_sem, BaseType_t *wok } } } + +#ifdef CONFIG_SUPPORT_TERMIOS +int tcgetattr(int fd, struct termios *p) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + struct _reent* r = __getreent(); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, tcgetattr, local_fd, p); + return ret; +} + +int tcsetattr(int fd, int optional_actions, const struct termios *p) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + struct _reent* r = __getreent(); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, tcsetattr, local_fd, optional_actions, p); + return ret; +} + +int tcdrain(int fd) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + struct _reent* r = __getreent(); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, tcdrain, local_fd); + return ret; +} + +int tcflush(int fd, int select) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + struct _reent* r = __getreent(); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, tcflush, local_fd, select); + return ret; +} + +int tcflow(int fd, int action) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + struct _reent* r = __getreent(); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, tcflow, local_fd, action); + return ret; +} + +pid_t tcgetsid(int fd) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + struct _reent* r = __getreent(); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, tcgetsid, local_fd); + return ret; +} + +int tcsendbreak(int fd, int duration) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + struct _reent* r = __getreent(); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, tcsendbreak, local_fd, duration); + return ret; +} +#endif // CONFIG_SUPPORT_TERMIOS diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index d53de75976..e407de4757 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -80,14 +80,15 @@ static esp_line_endings_t s_tx_mode = #endif // Newline conversion mode when receiving -static esp_line_endings_t s_rx_mode = +static esp_line_endings_t s_rx_mode[UART_NUM] = { [0 ... UART_NUM-1] = #if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF - ESP_LINE_ENDINGS_CRLF; + ESP_LINE_ENDINGS_CRLF #elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR - ESP_LINE_ENDINGS_CR; + ESP_LINE_ENDINGS_CR #else - ESP_LINE_ENDINGS_LF; + ESP_LINE_ENDINGS_LF #endif +}; static void uart_end_select(); @@ -213,9 +214,9 @@ static ssize_t uart_read(int fd, void* data, size_t size) while (received < size) { int c = uart_read_char(fd); if (c == '\r') { - if (s_rx_mode == ESP_LINE_ENDINGS_CR) { + if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CR) { c = '\n'; - } else if (s_rx_mode == ESP_LINE_ENDINGS_CRLF) { + } else if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CRLF) { /* look ahead */ int c2 = uart_read_char(fd); if (c2 == NONE) { @@ -420,6 +421,456 @@ static void uart_end_select() _lock_release(&s_one_select_lock); } +#ifdef CONFIG_SUPPORT_TERMIOS +static int uart_tcsetattr(int fd, int optional_actions, const struct termios *p) +{ + if (fd < 0 || fd >= UART_NUM) { + errno = EBADF; + return -1; + } + + if (p == NULL) { + errno = EINVAL; + return -1; + } + + switch (optional_actions) { + case TCSANOW: + // nothing to do + break; + case TCSADRAIN: + if (uart_wait_tx_done(fd, portMAX_DELAY) != ESP_OK) { + errno = EINVAL; + return -1; + } + // intentional fall-through to the next case + case TCSAFLUSH: + if (uart_flush_input(fd) != ESP_OK) { + errno = EINVAL; + return -1; + } + break; + default: + errno = EINVAL; + return -1; + } + + if (p->c_iflag & IGNCR) { + s_rx_mode[fd] = ESP_LINE_ENDINGS_CRLF; + } else if (p->c_iflag & ICRNL) { + s_rx_mode[fd] = ESP_LINE_ENDINGS_CR; + } else { + s_rx_mode[fd] = ESP_LINE_ENDINGS_LF; + } + + // output line endings are not supported because there is no alternative in termios for converting LF to CR + + { + uart_word_length_t data_bits; + const tcflag_t csize_bits = p->c_cflag & CSIZE; + + switch (csize_bits) { + case CS5: + data_bits = UART_DATA_5_BITS; + break; + case CS6: + data_bits = UART_DATA_6_BITS; + break; + case CS7: + data_bits = UART_DATA_7_BITS; + break; + case CS8: + data_bits = UART_DATA_8_BITS; + break; + default: + errno = EINVAL; + return -1; + } + + if (uart_set_word_length(fd, data_bits) != ESP_OK) { + errno = EINVAL; + return -1; + } + } + + if (uart_set_stop_bits(fd, (p->c_cflag & CSTOPB) ? UART_STOP_BITS_2 : UART_STOP_BITS_1) != ESP_OK) { + errno = EINVAL; + return -1; + } + + if (uart_set_parity(fd, (p->c_cflag & PARENB) ? + ((p->c_cflag & PARODD) ? UART_PARITY_ODD : UART_PARITY_EVEN) + : + UART_PARITY_DISABLE) != ESP_OK) { + errno = EINVAL; + return -1; + } + + if (p->c_cflag & (CBAUD | CBAUDEX)) { + if (p->c_ispeed != p->c_ospeed) { + errno = EINVAL; + return -1; + } else { + uint32_t b; + if (p->c_cflag & BOTHER) { + b = p->c_ispeed; + } else { + switch (p->c_ispeed) { + case B0: + b = 0; + break; + case B50: + b = 50; + break; + case B75: + b = 75; + break; + case B110: + b = 110; + break; + case B134: + b = 134; + break; + case B150: + b = 150; + break; + case B200: + b = 200; + break; + case B300: + b = 300; + break; + case B600: + b = 600; + break; + case B1200: + b = 1200; + break; + case B1800: + b = 1800; + break; + case B2400: + b = 2400; + break; + case B4800: + b = 4800; + break; + case B9600: + b = 9600; + break; + case B19200: + b = 19200; + break; + case B38400: + b = 38400; + break; + case B57600: + b = 57600; + break; + case B115200: + b = 115200; + break; + case B230400: + b = 230400; + break; + case B460800: + b = 460800; + break; + case B500000: + b = 500000; + break; + case B576000: + b = 576000; + break; + case B921600: + b = 921600; + break; + case B1000000: + b = 1000000; + break; + case B1152000: + b = 1152000; + break; + case B1500000: + b = 1500000; + break; + case B2000000: + b = 2000000; + break; + case B2500000: + b = 2500000; + break; + case B3000000: + b = 3000000; + break; + case B3500000: + b = 3500000; + break; + case B4000000: + b = 4000000; + break; + default: + errno = EINVAL; + return -1; + } + } + + if (uart_set_baudrate(fd, b) != ESP_OK) { + errno = EINVAL; + return -1; + } + } + } + + return 0; +} + +static int uart_tcgetattr(int fd, struct termios *p) +{ + if (fd < 0 || fd >= UART_NUM) { + errno = EBADF; + return -1; + } + + if (p == NULL) { + errno = EINVAL; + return -1; + } + + memset(p, 0, sizeof(struct termios)); + + if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CRLF) { + p->c_iflag |= IGNCR; + } else if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CR) { + p->c_iflag |= ICRNL; + } + + { + uart_word_length_t data_bits; + + if (uart_get_word_length(fd, &data_bits) != ESP_OK) { + errno = EINVAL; + return -1; + } + + p->c_cflag &= (~CSIZE); + + switch (data_bits) { + case UART_DATA_5_BITS: + p->c_cflag |= CS5; + break; + case UART_DATA_6_BITS: + p->c_cflag |= CS6; + break; + case UART_DATA_7_BITS: + p->c_cflag |= CS7; + break; + case UART_DATA_8_BITS: + p->c_cflag |= CS8; + break; + default: + errno = ENOSYS; + return -1; + } + } + + { + uart_stop_bits_t stop_bits; + if (uart_get_stop_bits(fd, &stop_bits) != ESP_OK) { + errno = EINVAL; + return -1; + } + + switch (stop_bits) { + case UART_STOP_BITS_1: + // nothing to do + break; + case UART_STOP_BITS_2: + p->c_cflag |= CSTOPB; + break; + default: + // UART_STOP_BITS_1_5 is unsupported by termios + errno = ENOSYS; + return -1; + } + } + + { + uart_parity_t parity_mode; + if (uart_get_parity(fd, &parity_mode) != ESP_OK) { + errno = EINVAL; + return -1; + } + + switch (parity_mode) { + case UART_PARITY_EVEN: + p->c_cflag |= PARENB; + break; + case UART_PARITY_ODD: + p->c_cflag |= (PARENB | PARODD); + break; + case UART_PARITY_DISABLE: + // nothing to do + break; + default: + errno = ENOSYS; + return -1; + } + } + + { + uint32_t baudrate; + if (uart_get_baudrate(fd, &baudrate) != ESP_OK) { + errno = EINVAL; + return -1; + } + + p->c_cflag |= (CBAUD | CBAUDEX); + + speed_t sp; + switch (baudrate) { + case 0: + sp = B0; + break; + case 50: + sp = B50; + break; + case 75: + sp = B75; + break; + case 110: + sp = B110; + break; + case 134: + sp = B134; + break; + case 150: + sp = B150; + break; + case 200: + sp = B200; + break; + case 300: + sp = B300; + break; + case 600: + sp = B600; + break; + case 1200: + sp = B1200; + break; + case 1800: + sp = B1800; + break; + case 2400: + sp = B2400; + break; + case 4800: + sp = B4800; + break; + case 9600: + sp = B9600; + break; + case 19200: + sp = B19200; + break; + case 38400: + sp = B38400; + break; + case 57600: + sp = B57600; + break; + case 115200: + sp = B115200; + break; + case 230400: + sp = B230400; + break; + case 460800: + sp = B460800; + break; + case 500000: + sp = B500000; + break; + case 576000: + sp = B576000; + break; + case 921600: + sp = B921600; + break; + case 1000000: + sp = B1000000; + break; + case 1152000: + sp = B1152000; + break; + case 1500000: + sp = B1500000; + break; + case 2000000: + sp = B2000000; + break; + case 2500000: + sp = B2500000; + break; + case 3000000: + sp = B3000000; + break; + case 3500000: + sp = B3500000; + break; + case 4000000: + sp = B4000000; + break; + default: + p->c_cflag |= BOTHER; + sp = baudrate; + break; + } + + p->c_ispeed = p->c_ospeed = sp; + } + + return 0; +} + +static int uart_tcdrain(int fd) +{ + if (fd < 0 || fd >= UART_NUM) { + errno = EBADF; + return -1; + } + + if (uart_wait_tx_done(fd, portMAX_DELAY) != ESP_OK) { + errno = EINVAL; + return -1; + } + + return 0; +} + +static int uart_tcflush(int fd, int select) +{ + if (fd < 0 || fd >= UART_NUM) { + errno = EBADF; + return -1; + } + + if (select == TCIFLUSH) { + if (uart_flush_input(fd) != ESP_OK) { + errno = EINVAL; + return -1; + } + } else { + // output flushing is not supported + errno = EINVAL; + return -1; + } + + return 0; +} +#endif // CONFIG_SUPPORT_TERMIOS + void esp_vfs_dev_uart_register() { esp_vfs_t vfs = { @@ -433,13 +884,21 @@ void esp_vfs_dev_uart_register() .access = &uart_access, .start_select = &uart_start_select, .end_select = &uart_end_select, +#ifdef CONFIG_SUPPORT_TERMIOS + .tcsetattr = &uart_tcsetattr, + .tcgetattr = &uart_tcgetattr, + .tcdrain = &uart_tcdrain, + .tcflush = &uart_tcflush, +#endif // CONFIG_SUPPORT_TERMIOS }; ESP_ERROR_CHECK(esp_vfs_register("/dev/uart", &vfs, NULL)); } void esp_vfs_dev_uart_set_rx_line_endings(esp_line_endings_t mode) { - s_rx_mode = mode; + for (int i = 0; i < UART_NUM; ++i) { + s_rx_mode[i] = mode; + } } void esp_vfs_dev_uart_set_tx_line_endings(esp_line_endings_t mode)