vfs: support for blocking reads, more newline conversion options

Previously VFS driver for UART could only use simple non-blocking
functions to read from and write to the UART. UART driver provides more
complex blocking and interrupt-driven functions, which can be used
instead.
This commit adds optional support for using UART driver's functions.

Also added is a more flexible mechanism for configuring newline
conversion rules on input and output.

This commit also fixes a bug that all UARTs shared one static variable
used as a character buffer in newline conversion code. This variable is
changed to be per-UART.
This commit is contained in:
Ivan Grokhotkov 2017-08-15 16:23:23 +08:00
parent a8075ea140
commit 489c523870
4 changed files with 279 additions and 62 deletions

View file

@ -179,20 +179,53 @@ config IPC_TASK_STACK_SIZE
It can be shrunk if you are sure that you do not use any custom
IPC functionality.
config NEWLIB_STDOUT_ADDCR
bool "Standard-out output adds carriage return before newline"
default y
choice NEWLIB_STDOUT_LINE_ENDING
prompt "Line ending for UART output"
default NEWLIB_STDOUT_LINE_ENDING_CRLF
help
Most people are used to end their printf strings with a newline. If this
is sent as is to the serial port, most terminal programs will only move the
cursor one line down, not also move it to the beginning of the line. This
is usually done by an added CR character. Enabling this will make the
standard output code automatically add a CR character before a LF.
This option allows configuring the desired line endings sent to UART
when a newline ('\n', LF) appears on stdout.
Three options are possible:
With this option enabled, C standard library functions which read from UART
(like scanf) will convert "\r\n" character sequences back to "\n".
CRLF: whenever LF is encountered, prepend it with CR
LF: no modification is applied, stdout is sent as is
CR: each occurence of LF is replaced with CR
This option doesn't affect behavior of the UART driver (drivers/uart.h).
config NEWLIB_STDOUT_LINE_ENDING_CRLF
bool "CRLF"
config NEWLIB_STDOUT_LINE_ENDING_LF
bool "LF"
config NEWLIB_STDOUT_LINE_ENDING_CR
bool "CR"
endchoice
choice NEWLIB_STDIN_LINE_ENDING
prompt "Line ending for UART input"
default NEWLIB_STDIN_LINE_ENDING_CR
help
This option allows configuring which input sequence on UART produces
a newline ('\n', LF) on stdin.
Three options are possible:
CRLF: CRLF is converted to LF
LF: no modification is applied, input is sent to stdin as is
CR: each occurence of CR is replaced with LF
This option doesn't affect behavior of the UART driver (drivers/uart.h).
config NEWLIB_STDIN_LINE_ENDING_CRLF
bool "CRLF"
config NEWLIB_STDIN_LINE_ENDING_LF
bool "LF"
config NEWLIB_STDIN_LINE_ENDING_CR
bool "CR"
endchoice
config NEWLIB_NANO_FORMAT
bool "Enable 'nano' formatting options for printf/scanf family"

View file

@ -140,7 +140,13 @@ If "UART for console output" menuconfig option is not set to "None", then ``stdi
Writing to ``stdout`` or ``stderr`` will send characters to the UART transmit FIFO. Reading from ``stdin`` will retrieve characters from the UART receive FIFO.
Note that while writing to ``stdout`` or ``stderr`` will block until all characters are put into the FIFO, reading from ``stdin`` is non-blocking. The function which reads from UART will get all the characters present in the FIFO (if any), and return. I.e. doing ``fscanf("%d\n", &var);`` may not have desired results. This is a temporary limitation which will be removed once ``fcntl`` is added to the VFS interface.
By default, VFS uses simple functions for reading from and writing to UART. Writes busy-wait until all data is put into UART FIFO, and reads are non-blocking, returning only the data present in the FIFO. Because of this non-blocking read behavior, higher level C library calls, such as ``fscanf("%d\n", &var);`` may not have desired results.
Applications which use UART driver may instruct VFS to use the driver's interrupt driven, blocking read and write functions instead. This can be done using a call to ``esp_vfs_dev_uart_use_driver`` function. It is also possible to revert to the basic non-blocking functions using a call to ``esp_vfs_dev_uart_use_nonblocking``.
VFS also provides optional newline conversion feature for input and output. Internally, most applications send and receive lines terminated by LF (''\n'') character. Different terminal programs may require different line termination, such as CR or CRLF. Applications can configure this separately for input and output either via menuconfig, or by calls to ``esp_vfs_dev_uart_set_rx_line_endings`` and ``esp_vfs_dev_uart_set_tx_line_endings`` functions.
Standard streams and FreeRTOS tasks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2017 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.
@ -17,6 +17,15 @@
#include "esp_vfs.h"
/**
* @brief Line ending settings
*/
typedef enum {
ESP_LINE_ENDINGS_CRLF,//!< CR + LF
ESP_LINE_ENDINGS_CR, //!< CR
ESP_LINE_ENDINGS_LF, //!< LF
} esp_line_endings_t;
/**
* @brief add /dev/uart virtual filesystem driver
*
@ -24,5 +33,52 @@
*/
void esp_vfs_dev_uart_register();
/**
* @brief Set the line endings expected to be received on UART
*
* This specifies the conversion between line endings received on UART and
* newlines ('\n', LF) passed into stdin:
*
* - ESP_LINE_ENDINGS_CRLF: convert CRLF to LF
* - ESP_LINE_ENDINGS_CR: convert CR to LF
* - ESP_LINE_ENDINGS_LF: no modification
*
* @note this function is not thread safe w.r.t. reading from UART
*
* @param mode line endings expected on UART
*/
void esp_vfs_dev_uart_set_rx_line_endings(esp_line_endings_t mode);
/**
* @brief Set the line endings to sent to UART
*
* This specifies the conversion between newlines ('\n', LF) on stdout and line
* endings sent over UART:
*
* - ESP_LINE_ENDINGS_CRLF: convert LF to CRLF
* - ESP_LINE_ENDINGS_CR: convert LF to CR
* - ESP_LINE_ENDINGS_LF: no modification
*
* @note this function is not thread safe w.r.t. writing to UART
*
* @param mode line endings to send to UART
*/
void esp_vfs_dev_uart_set_tx_line_endings(esp_line_endings_t mode);
/**
* @brief set VFS to use simple functions for reading and writing UART
* Read is non-blocking, write is busy waiting until TX FIFO has enough space.
* These functions are used by default.
* @param uart_num UART peripheral number
*/
void esp_vfs_dev_uart_use_nonblocking(int uart_num);
/**
* @brief set VFS to use UART driver for reading and writing
* @note application must configure UART driver before calling these functions
* With these functions, read and write are blocking and interrupt-driven.
* @param uart_num UART peripheral number
*/
void esp_vfs_dev_uart_use_driver(int uart_num);
#endif //__ESP_VFS_DEV_H__

View file

@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2017 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.
@ -13,17 +13,74 @@
// limitations under the License.
#include <string.h>
#include <stdbool.h>
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "esp_attr.h"
#include "sys/errno.h"
#include "sys/lock.h"
#include "soc/uart_struct.h"
#include "driver/uart.h"
#include "sdkconfig.h"
static uart_dev_t* s_uarts[3] = {&UART0, &UART1, &UART2};
static _lock_t s_uart_locks[3]; // per-UART locks, lazily initialized
// TODO: make the number of UARTs chip dependent
#define UART_NUM 3
static int IRAM_ATTR uart_open(const char * path, int flags, int mode)
// Token signifying that no character is available
#define NONE -1
// UART write bytes function type
typedef void (*tx_func_t)(int, int);
// UART read bytes function type
typedef int (*rx_func_t)(int);
// Basic functions for sending and receiving bytes over UART
static void uart_tx_char(int fd, int c);
static int uart_rx_char(int fd);
// Functions for sending and receiving bytes which use UART driver
static void uart_tx_char_via_driver(int fd, int c);
static int uart_rx_char_via_driver(int fd);
// Pointers to UART peripherals
static uart_dev_t* s_uarts[UART_NUM] = {&UART0, &UART1, &UART2};
// per-UART locks, lazily initialized
static _lock_t s_uart_locks[UART_NUM];
// One-character buffer used for newline conversion code, per UART
static int s_peek_char[UART_NUM] = { NONE, NONE, NONE };
// Newline conversion mode when transmitting
static esp_line_endings_t s_tx_mode =
#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
ESP_LINE_ENDINGS_CRLF;
#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR
ESP_LINE_ENDINGS_CR;
#else
ESP_LINE_ENDINGS_LF;
#endif
// Newline conversion mode when receiving
static esp_line_endings_t s_rx_mode =
#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF
ESP_LINE_ENDINGS_CRLF;
#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR
ESP_LINE_ENDINGS_CR;
#else
ESP_LINE_ENDINGS_LF;
#endif
// Functions used to write bytes to UART. Default to "basic" functions.
static tx_func_t s_uart_tx_func[UART_NUM] = {
&uart_tx_char, &uart_tx_char, &uart_tx_char
};
// Functions used to read bytes from UART. Default to "basic" functions.
static rx_func_t s_uart_rx_func[UART_NUM] = {
&uart_rx_char, &uart_rx_char, &uart_rx_char
};
static int uart_open(const char * path, int flags, int mode)
{
// this is fairly primitive, we should check if file is opened read only,
// and error out if write is requested
@ -38,81 +95,120 @@ static int IRAM_ATTR uart_open(const char * path, int flags, int mode)
return -1;
}
static void IRAM_ATTR uart_tx_char(uart_dev_t* uart, int c)
static void uart_tx_char(int fd, int c)
{
uart_dev_t* uart = s_uarts[fd];
while (uart->status.txfifo_cnt >= 127) {
;
}
uart->fifo.rw_byte = c;
}
static void uart_tx_char_via_driver(int fd, int c)
{
char ch = (char) c;
uart_write_bytes(fd, &ch, 1);
}
static ssize_t IRAM_ATTR uart_write(int fd, const void * data, size_t size)
static int uart_rx_char(int fd)
{
uart_dev_t* uart = s_uarts[fd];
if (uart->status.rxfifo_cnt == 0) {
return NONE;
}
return uart->fifo.rw_byte;
}
static int uart_rx_char_via_driver(int fd)
{
uint8_t c;
int n = uart_read_bytes(fd, &c, 1, portMAX_DELAY);
if (n == 0) {
return NONE;
}
return c;
}
static ssize_t uart_write(int fd, const void * data, size_t size)
{
assert(fd >=0 && fd < 3);
const char *data_c = (const char *)data;
uart_dev_t* uart = s_uarts[fd];
/*
* Even though newlib does stream locking on each individual stream, we need
/* Even though newlib does stream locking on each individual stream, we need
* a dedicated UART lock if two streams (stdout and stderr) point to the
* same UART.
*/
_lock_acquire_recursive(&s_uart_locks[fd]);
for (size_t i = 0; i < size; i++) {
#if CONFIG_NEWLIB_STDOUT_ADDCR
if (data_c[i]=='\n') {
uart_tx_char(uart, '\r');
int c = data_c[i];
if (c == '\n' && s_tx_mode != ESP_LINE_ENDINGS_LF) {
s_uart_tx_func[fd](fd, '\r');
if (s_tx_mode == ESP_LINE_ENDINGS_CR) {
continue;
}
}
#endif
uart_tx_char(uart, data_c[i]);
s_uart_tx_func[fd](fd, c);
}
_lock_release_recursive(&s_uart_locks[fd]);
return size;
}
static ssize_t IRAM_ATTR uart_read(int fd, void* data, size_t size)
/* Helper function which returns a previous character or reads a new one from
* UART. Previous character can be returned ("pushed back") using
* uart_return_char function.
*/
static int uart_read_char(int fd)
{
/* return character from peek buffer, if it is there */
if (s_peek_char[fd] != NONE) {
int c = s_peek_char[fd];
s_peek_char[fd] = NONE;
return c;
}
return s_uart_rx_func[fd](fd);
}
/* Push back a character; it will be returned by next call to uart_read_char */
static void uart_return_char(int fd, int c)
{
assert(s_peek_char[fd] == NONE);
s_peek_char[fd] = c;
}
static ssize_t uart_read(int fd, void* data, size_t size)
{
assert(fd >=0 && fd < 3);
uint8_t *data_c = (uint8_t *) data;
uart_dev_t* uart = s_uarts[fd];
char *data_c = (char *) data;
size_t received = 0;
_lock_acquire_recursive(&s_uart_locks[fd]);
while (uart->status.rxfifo_cnt > 0 && received < size) {
uint8_t c = uart->fifo.rw_byte;
#if CONFIG_NEWLIB_STDOUT_ADDCR
/* Convert \r\n sequences to \n.
* If \r is received, it is put into 'buffered_char' until the next
* character is received. Then depending on the character, we either
* drop \r (if the next one is \n) or output \r and then proceed to output
* the new character.
*/
const int NONE = -1;
static int buffered_char = NONE;
if (buffered_char != NONE) {
if (buffered_char == '\r' && c == '\n') {
buffered_char = NONE;
} else {
data_c[received] = buffered_char;
buffered_char = NONE;
++received;
if (received == size) {
/* We have placed the buffered character into the output buffer
* but there won't be enough space for the newly received one.
* Keep the new character in buffered_char until read is called
* again.
*/
buffered_char = c;
while (received < size) {
int c = uart_read_char(fd);
if (c == '\r') {
if (s_rx_mode == ESP_LINE_ENDINGS_CR) {
c = '\n';
} else if (s_rx_mode == ESP_LINE_ENDINGS_CRLF) {
/* look ahead */
int c2 = uart_read_char(fd);
if (c2 == NONE) {
/* could not look ahead, put the current character back */
uart_return_char(fd, c);
break;
}
if (c2 == '\n') {
/* this was \r\n sequence. discard \r, return \n */
c = '\n';
} else {
/* \r followed by something else. put the second char back,
* it will be processed on next iteration. return \r now.
*/
uart_return_char(fd, c2);
}
}
}
if (c == '\r') {
buffered_char = c;
continue;
}
#endif //CONFIG_NEWLIB_STDOUT_ADDCR
data_c[received] = c;
data_c[received] = (char) c;
++received;
if (c == '\n') {
break;
}
}
_lock_release_recursive(&s_uart_locks[fd]);
if (received > 0) {
@ -122,14 +218,14 @@ static ssize_t IRAM_ATTR uart_read(int fd, void* data, size_t size)
return -1;
}
static int IRAM_ATTR uart_fstat(int fd, struct stat * st)
static int uart_fstat(int fd, struct stat * st)
{
assert(fd >=0 && fd < 3);
st->st_mode = S_IFCHR;
return 0;
}
static int IRAM_ATTR uart_close(int fd)
static int uart_close(int fd)
{
assert(fd >=0 && fd < 3);
return 0;
@ -153,3 +249,29 @@ void esp_vfs_dev_uart_register()
};
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;
}
void esp_vfs_dev_uart_set_tx_line_endings(esp_line_endings_t mode)
{
s_tx_mode = mode;
}
void esp_vfs_dev_uart_use_nonblocking(int fd)
{
_lock_acquire_recursive(&s_uart_locks[fd]);
s_uart_tx_func[fd] = uart_tx_char;
s_uart_rx_func[fd] = uart_rx_char;
_lock_release_recursive(&s_uart_locks[fd]);
}
void esp_vfs_dev_uart_use_driver(int fd)
{
_lock_acquire_recursive(&s_uart_locks[fd]);
s_uart_tx_func[fd] = uart_tx_char_via_driver;
s_uart_rx_func[fd] = uart_rx_char_via_driver;
_lock_release_recursive(&s_uart_locks[fd]);
}