From e36706d776a98bdd8735ddb2d262cd061f7d5a54 Mon Sep 17 00:00:00 2001 From: dongheng Date: Thu, 7 Mar 2019 14:35:15 +0800 Subject: [PATCH] feat(vfs): Bring vfs from esp-idf Commit ID: e1e82c89 --- .../esp8266/include/driver/uart_select.h | 49 + .../port/esp8266/include/arch/vfs_lwip.h} | 22 +- components/lwip/port/vfs_lwip.c | 74 ++ .../newlib/newlib/port/include/sys/termios.h | 296 +++++ components/newlib/newlib/port/select.c | 64 + components/newlib/newlib/port/termios.c | 54 + components/vfs/CMakeLists.txt | 7 + components/vfs/Kconfig | 18 + components/vfs/README.rst | 170 +++ components/vfs/component.mk | 4 +- components/vfs/include/esp_vfs.h | 386 ++++++ components/vfs/include/esp_vfs_dev.h | 89 ++ components/vfs/include/sys/dirent.h | 55 + components/vfs/include/sys/ioctl.h | 4 - components/vfs/src/ioctl.c | 33 - components/vfs/vfs.c | 1098 +++++++++++++++++ components/vfs/vfs_uart.c | 940 ++++++++++++++ .../peripherals/uart_select/CMakeLists.txt | 6 + examples/peripherals/uart_select/Makefile | 8 + examples/peripherals/uart_select/README.md | 13 + .../uart_select/main/CMakeLists.txt | 4 + .../peripherals/uart_select/main/component.mk | 3 + .../main/uart_select_example_main.c | 93 ++ 23 files changed, 3435 insertions(+), 55 deletions(-) create mode 100644 components/esp8266/include/driver/uart_select.h rename components/{vfs/src/fcntl.c => lwip/port/esp8266/include/arch/vfs_lwip.h} (64%) create mode 100644 components/lwip/port/vfs_lwip.c create mode 100644 components/newlib/newlib/port/include/sys/termios.h create mode 100644 components/newlib/newlib/port/select.c create mode 100644 components/newlib/newlib/port/termios.c create mode 100644 components/vfs/CMakeLists.txt create mode 100644 components/vfs/Kconfig create mode 100644 components/vfs/README.rst mode change 100755 => 100644 components/vfs/component.mk create mode 100644 components/vfs/include/esp_vfs.h create mode 100644 components/vfs/include/esp_vfs_dev.h create mode 100644 components/vfs/include/sys/dirent.h delete mode 100644 components/vfs/src/ioctl.c create mode 100644 components/vfs/vfs.c create mode 100644 components/vfs/vfs_uart.c create mode 100644 examples/peripherals/uart_select/CMakeLists.txt create mode 100644 examples/peripherals/uart_select/Makefile create mode 100644 examples/peripherals/uart_select/README.md create mode 100644 examples/peripherals/uart_select/main/CMakeLists.txt create mode 100644 examples/peripherals/uart_select/main/component.mk create mode 100644 examples/peripherals/uart_select/main/uart_select_example_main.c diff --git a/components/esp8266/include/driver/uart_select.h b/components/esp8266/include/driver/uart_select.h new file mode 100644 index 00000000..24f06c1d --- /dev/null +++ b/components/esp8266/include/driver/uart_select.h @@ -0,0 +1,49 @@ + +// 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. + +#ifndef _UART_SELECT_H_ +#define _UART_SELECT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "driver/uart.h" + +typedef enum { + UART_SELECT_READ_NOTIF, + UART_SELECT_WRITE_NOTIF, + UART_SELECT_ERROR_NOTIF, +} uart_select_notif_t; + +typedef void (*uart_select_notif_callback_t)(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken); + +/** + * @brief Set notification callback function for select() events + * @param uart_num UART port number + * @param uart_select_notif_callback callback function + */ +void uart_set_select_notif_callback(uart_port_t uart_num, uart_select_notif_callback_t uart_select_notif_callback); + +/** + * @brief Get mutex guarding select() notifications + */ +portMUX_TYPE *uart_get_selectlock(); + +#ifdef __cplusplus +} +#endif + +#endif //_UART_SELECT_H_ diff --git a/components/vfs/src/fcntl.c b/components/lwip/port/esp8266/include/arch/vfs_lwip.h similarity index 64% rename from components/vfs/src/fcntl.c rename to components/lwip/port/esp8266/include/arch/vfs_lwip.h index b79b389d..1957304b 100644 --- a/components/vfs/src/fcntl.c +++ b/components/lwip/port/esp8266/include/arch/vfs_lwip.h @@ -1,4 +1,4 @@ -// Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD +// Copyright 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. @@ -12,20 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#ifdef __cplusplus +extern "C" { +#endif -int fcntl(int fd, int request, ...) -{ - int val, ret; - va_list va; +void esp_vfs_lwip_sockets_register(); - va_start(va, request); - - val = va_arg(va, int); - ret = lwip_fcntl(fd, request, val); - - va_end(va); - - return ret; +#ifdef __cplusplus } +#endif diff --git a/components/lwip/port/vfs_lwip.c b/components/lwip/port/vfs_lwip.c new file mode 100644 index 00000000..54d71912 --- /dev/null +++ b/components/lwip/port/vfs_lwip.c @@ -0,0 +1,74 @@ +// Copyright 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. +// 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 +#include +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_vfs_dev.h" +#include "esp_attr.h" +#include "soc/uart_struct.h" +#include "lwip/sockets.h" +#include "sdkconfig.h" +#include "lwip/sys.h" + +_Static_assert(MAX_FDS >= CONFIG_LWIP_MAX_SOCKETS, "MAX_FDS < CONFIG_LWIP_MAX_SOCKETS"); + +static void lwip_stop_socket_select() +{ + sys_sem_signal(sys_thread_sem_get()); //socket_select will return +} + +static void lwip_stop_socket_select_isr(BaseType_t *woken) +{ + if (sys_sem_signal_isr(sys_thread_sem_get()) && woken) { + *woken = pdTRUE; + } +} + +static int lwip_fcntl_r_wrapper(int fd, int cmd, va_list args) +{ + return lwip_fcntl_r(fd, cmd, va_arg(args, int)); +} + +static int lwip_ioctl_r_wrapper(int fd, int cmd, va_list args) +{ + return lwip_ioctl_r(fd, cmd, va_arg(args, void *)); +} + +void esp_vfs_lwip_sockets_register() +{ + esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &lwip_write_r, + .open = NULL, + .fstat = NULL, + .close = &lwip_close_r, + .read = &lwip_read_r, + .fcntl = &lwip_fcntl_r_wrapper, + .ioctl = &lwip_ioctl_r_wrapper, + .socket_select = &lwip_select, + .stop_socket_select = &lwip_stop_socket_select, + .stop_socket_select_isr = &lwip_stop_socket_select_isr, + }; + /* Non-LWIP file descriptors are from 0 to (LWIP_SOCKET_OFFSET-1). LWIP + * file descriptors are registered from LWIP_SOCKET_OFFSET to + * MAX_FDS-1. + */ + + ESP_ERROR_CHECK(esp_vfs_register_fd_range(&vfs, NULL, LWIP_SOCKET_OFFSET, MAX_FDS)); +} diff --git a/components/newlib/newlib/port/include/sys/termios.h b/components/newlib/newlib/port/include/sys/termios.h new file mode 100644 index 00000000..fd0eb5ca --- /dev/null +++ b/components/newlib/newlib/port/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/newlib/port/select.c b/components/newlib/newlib/port/select.c new file mode 100644 index 00000000..60631506 --- /dev/null +++ b/components/newlib/newlib/port/select.c @@ -0,0 +1,64 @@ +// 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 +#include "esp_vfs.h" +#include "sdkconfig.h" + +#ifdef CONFIG_USE_ONLY_LWIP_SELECT +#include "lwip/sockets.h" + +#ifdef CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT +#define LOG_LOCAL_LEVEL ESP_LOG_NONE +#endif //CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT +#include "esp_log.h" + +static const char *TAG = "newlib_select"; + +static void log_fd_set(const char *fds_name, const fd_set *fds) +{ + if (fds_name && fds) { + ESP_LOGD(TAG, "FDs in %s =", fds_name); + for (int i = 0; i < MAX_FDS; ++i) { + if (FD_ISSET(i, fds)) { + ESP_LOGD(TAG, "%d", i); + } + } + } +} +#endif //CONFIG_USE_ONLY_LWIP_SELECT + +int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) +{ +#ifdef CONFIG_USE_ONLY_LWIP_SELECT + ESP_LOGD(TAG, "lwip_select starts with nfds = %d", nfds); + if (timeout) { + ESP_LOGD(TAG, "timeout is %lds + %ldus", timeout->tv_sec, timeout->tv_usec); + } + log_fd_set("readfds", readfds); + log_fd_set("writefds", writefds); + log_fd_set("errorfds", errorfds); + + int ret = lwip_select(nfds, readfds, writefds, errorfds, timeout); + + ESP_LOGD(TAG, "lwip_select returns %d", ret); + log_fd_set("readfds", readfds); + log_fd_set("writefds", writefds); + log_fd_set("errorfds", errorfds); + + return ret; +#else + return esp_vfs_select(nfds, readfds, writefds, errorfds, timeout); +#endif +} diff --git a/components/newlib/newlib/port/termios.c b/components/newlib/newlib/port/termios.c new file mode 100644 index 00000000..bccd5bf8 --- /dev/null +++ b/components/newlib/newlib/port/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/CMakeLists.txt b/components/vfs/CMakeLists.txt new file mode 100644 index 00000000..77083dc2 --- /dev/null +++ b/components/vfs/CMakeLists.txt @@ -0,0 +1,7 @@ +set(COMPONENT_SRCS "vfs.c" + "vfs_uart.c") +set(COMPONENT_ADD_INCLUDEDIRS "include") + +set(COMPONENT_REQUIRES) + +register_component() diff --git a/components/vfs/Kconfig b/components/vfs/Kconfig new file mode 100644 index 00000000..19a565b2 --- /dev/null +++ b/components/vfs/Kconfig @@ -0,0 +1,18 @@ +menu "Virtual file system" + +config SUPPRESS_SELECT_DEBUG_OUTPUT + bool "Suppress select() related debug outputs" + default y + help + Select() related functions might produce an unconveniently lot of + debug outputs when one sets the default log level to DEBUG or higher. + 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/README.rst b/components/vfs/README.rst new file mode 100644 index 00000000..e7e50fd6 --- /dev/null +++ b/components/vfs/README.rst @@ -0,0 +1,170 @@ +Virtual filesystem component +============================ + +Overview +-------- + +Virtual filesystem (VFS) component provides a unified interface for drivers which can perform operations on file-like objects. This can be a real filesystems (FAT, SPIFFS, etc.), or device drivers which exposes file-like interface. + +This component allows C library functions, such as fopen and fprintf, to work with FS drivers. At high level, each FS driver is associated with some path prefix. When one of C library functions needs to open a file, VFS component searches for the FS driver associated with the file's path, and forwards the call to that driver. VFS also forwards read, write, and other calls for the given file to the same FS driver. + +For example, one can register a FAT filesystem driver with ``/fat`` prefix, and call ``fopen("/fat/file.txt", "w")``. VFS component will then call ``open`` function of FAT driver and pass ``/file.txt`` argument to it (and appropriate mode flags). All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT driver. + +FS registration +--------------- + + + +To register an FS driver, application needs to define in instance of :cpp:type:`esp_vfs_t` structure and populate it with function pointers to FS APIs: + +.. highlight:: c + +:: + + esp_vfs_t myfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &myfs_write, + .open = &myfs_open, + .fstat = &myfs_fstat, + .close = &myfs_close, + .read = &myfs_read, + }; + + ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); + +Depending on the way FS driver declares its APIs, either ``read``, ``write``, etc., or ``read_p``, ``write_p``, etc. should be used. + +Case 1: API functions are declared without an extra context pointer (FS driver is a singleton):: + + ssize_t myfs_write(int fd, const void * data, size_t size); + + // In definition of esp_vfs_t: + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &myfs_write, + // ... other members initialized + + // When registering FS, context pointer (third argument) is NULL: + ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); + +Case 2: API functions are declared with an extra context pointer (FS driver supports multiple instances):: + + ssize_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size); + + // In definition of esp_vfs_t: + .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .write_p = &myfs_write, + // ... other members initialized + + // When registering FS, pass the FS context pointer into the third argument + // (hypothetical myfs_mount function is used for illustrative purposes) + myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size); + ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1)); + + // Can register another instance: + myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size); + ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2)); + +Synchronous input/output multiplexing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you want to use synchronous input/output multiplexing by :cpp:func:`select` +then you need to register the VFS with :cpp:func:`start_select` and +:cpp:func:`end_select` functions similarly to the following example: + +.. highlight:: c + +:: + + // In definition of esp_vfs_t: + .start_select = &uart_start_select, + .end_select = &uart_end_select, + // ... other members initialized + +:cpp:func:`start_select` is called for setting up the environment for +detection of read/write/error conditions on file descriptors belonging to the +given VFS. :cpp:func:`end_select` is called to stop/deinitialize/free the +environment which was setup by :cpp:func:`start_select`. Please refer to the +reference implementation for the UART peripheral in +:component_file:`vfs/vfs_uart.c` and most particularly to functions +:cpp:func:`esp_vfs_dev_uart_register`, :cpp:func:`uart_start_select` and +:cpp:func:`uart_end_select`. + +Examples demonstrating the use of :cpp:func:`select` with VFS file descriptors +are the :example:`peripherals/uart_select` and the :example:`system/select` +examples. + +If :cpp:func:`select` is used for socket file descriptors only then one can +enable the :envvar:`CONFIG_USE_ONLY_LWIP_SELECT` option which can reduce the code +size and improve performance. + +Paths +----- + +Each registered FS has a path prefix associated with it. This prefix may be considered a "mount point" of this partition. + +In case when mount points are nested, the mount point with the longest matching path prefix is used when opening the file. For instance, suppose that the following filesystems are registered in VFS: + +- FS 1 on /data +- FS 2 on /data/static + +Then: + +- FS 1 will be used when opening a file called ``/data/log.txt`` +- FS 2 will be used when opening a file called ``/data/static/index.html`` +- Even if ``/index.html"`` doesn't exist in FS 2, FS 1 will *not* be searched for ``/static/index.html``. + +As a general rule, mount point names must start with the path separator (``/``) and must contain at least one character after path separator. However an empty mount point name is also supported, and may be used in cases when application needs to provide "fallback" filesystem, or override VFS functionality altogether. Such filesystem will be used if no prefix matches the path given. + +VFS does not handle dots (``.``) in path names in any special way. VFS does not treat ``..`` as a reference to the parent directory. I.e. in the above example, using a path ``/data/static/../log.txt`` will not result in a call to FS 1 to open ``/log.txt``. Specific FS drivers (such as FATFS) may handle dots in file names differently. + +When opening files, FS driver will only be given relative path to files. For example: + +- ``myfs`` driver is registered with ``/data`` as path prefix +- and application calls ``fopen("/data/config.json", ...)`` +- then VFS component will call ``myfs_open("/config.json", ...)``. +- ``myfs`` driver will open ``/config.json`` file + +VFS doesn't impose a limit on total file path length, but it does limit FS path prefix to ``ESP_VFS_PATH_MAX`` characters. Individual FS drivers may have their own filename length limitations. + +File descriptors +---------------- + +File descriptors are small positive integers from ``0`` to ``FD_SETSIZE - 1`` where ``FD_SETSIZE`` is defined in newlib's ``sys/types.h``. The largest file descriptors (configured by ``CONFIG_LWIP_MAX_SOCKETS``) are reserved for sockets. The VFS component contains a lookup-table called ``s_fd_table`` for mapping global file descriptors to VFS driver indexes registered in the ``s_vfs`` array. + +Standard IO streams (stdin, stdout, stderr) +------------------------------------------- + +If "UART for console output" menuconfig option is not set to "None", then ``stdin``, ``stdout``, and ``stderr`` are configured to read from, and write to, a UART. It is possible to use UART0 or UART1 for standard IO. By default, UART0 is used, with 115200 baud rate, TX pin is GPIO1 and RX pin is GPIO3. These parameters can be changed in menuconfig. + +Writing to ``stdout`` or ``stderr`` will send characters to the UART transmit FIFO. Reading from ``stdin`` will retrieve characters from the UART receive FIFO. + +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 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``FILE`` objects for ``stdin``, ``stdout``, and ``stderr`` are shared between all FreeRTOS tasks, but the pointers to these objects are are stored in per-task ``struct _reent``. The following code: + +.. highlight:: c + +:: + + fprintf(stderr, "42\n"); + +actually is translated to to this (by the preprocessor):: + + fprintf(__getreent()->_stderr, "42\n"); + +where the ``__getreent()`` function returns a per-task pointer to ``struct _reent`` (:component_file:`newlib/include/sys/reent.h#L370-L417`). This structure is allocated on the TCB of each task. When a task is initialized, ``_stdin``, ``_stdout`` and ``_stderr`` members of ``struct _reent`` are set to the values of ``_stdin``, ``_stdout`` and ``_stderr`` of ``_GLOBAL_REENT`` (i.e. the structure which is used before FreeRTOS is started). + +Such a design has the following consequences: + +- It is possible to set ``stdin``, ``stdout``, and ``stderr`` for any given task without affecting other tasks, e.g. by doing ``stdin = fopen("/dev/uart/1", "r")``. +- Closing default ``stdin``, ``stdout``, or ``stderr`` using ``fclose`` will close the ``FILE`` stream object — this will affect all other tasks. +- To change the default ``stdin``, ``stdout``, ``stderr`` streams for new tasks, modify ``_GLOBAL_REENT->_stdin`` (``_stdout``, ``_stderr``) before creating the task. diff --git a/components/vfs/component.mk b/components/vfs/component.mk old mode 100755 new mode 100644 index 2341ae7b..c2c4c03a --- a/components/vfs/component.mk +++ b/components/vfs/component.mk @@ -1,7 +1,5 @@ # # Component Makefile # +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) -COMPONENT_ADD_INCLUDEDIRS := include - -COMPONENT_SRCDIRS := src diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h new file mode 100644 index 00000000..d7467d22 --- /dev/null +++ b/components/vfs/include/esp_vfs.h @@ -0,0 +1,386 @@ +// Copyright 2015-2016 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. + +#ifndef __ESP_VFS_H__ +#define __ESP_VFS_H__ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _SYS_TYPES_FD_SET +#error "VFS should be used with FD_SETSIZE and FD_SET from sys/types.h" +#endif + +/** + * Maximum number of (global) file descriptors. + */ +#define MAX_FDS FD_SETSIZE /* for compatibility with fd_set and select() */ + +/** + * Maximum length of path prefix (not including zero terminator) + */ +#define ESP_VFS_PATH_MAX 15 + +/** + * Default value of flags member in esp_vfs_t structure. + */ +#define ESP_VFS_FLAG_DEFAULT 0 + +/** + * Flag which indicates that FS needs extra context pointer in syscalls. + */ +#define ESP_VFS_FLAG_CONTEXT_PTR 1 + +/* + * @brief VFS identificator used for esp_vfs_register_with_id() + */ +typedef int esp_vfs_id_t; + +/** + * @brief VFS definition structure + * + * This structure should be filled with pointers to corresponding + * FS driver functions. + * + * VFS component will translate all FDs so that the filesystem implementation + * sees them starting at zero. The caller sees a global FD which is prefixed + * with an pre-filesystem-implementation. + * + * Some FS implementations expect some state (e.g. pointer to some structure) + * to be passed in as a first argument. For these implementations, + * populate the members of this structure which have _p suffix, set + * flags member to ESP_VFS_FLAG_CONTEXT_PTR and provide the context pointer + * to esp_vfs_register function. + * If the implementation doesn't use this extra argument, populate the + * members without _p suffix and set flags member to ESP_VFS_FLAG_DEFAULT. + * + * If the FS driver doesn't provide some of the functions, set corresponding + * members to NULL. + */ +typedef struct +{ + int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT */ + union { + ssize_t (*write_p)(void* p, int fd, const void * data, size_t size); + ssize_t (*write)(int fd, const void * data, size_t size); + }; + union { + off_t (*lseek_p)(void* p, int fd, off_t size, int mode); + off_t (*lseek)(int fd, off_t size, int mode); + }; + union { + ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size); + ssize_t (*read)(int fd, void * dst, size_t size); + }; + union { + int (*open_p)(void* ctx, const char * path, int flags, int mode); + int (*open)(const char * path, int flags, int mode); + }; + union { + int (*close_p)(void* ctx, int fd); + int (*close)(int fd); + }; + union { + int (*fstat_p)(void* ctx, int fd, struct stat * st); + int (*fstat)(int fd, struct stat * st); + }; + union { + int (*stat_p)(void* ctx, const char * path, struct stat * st); + int (*stat)(const char * path, struct stat * st); + }; + union { + int (*link_p)(void* ctx, const char* n1, const char* n2); + int (*link)(const char* n1, const char* n2); + }; + union { + int (*unlink_p)(void* ctx, const char *path); + int (*unlink)(const char *path); + }; + union { + int (*rename_p)(void* ctx, const char *src, const char *dst); + int (*rename)(const char *src, const char *dst); + }; + union { + DIR* (*opendir_p)(void* ctx, const char* name); + DIR* (*opendir)(const char* name); + }; + union { + struct dirent* (*readdir_p)(void* ctx, DIR* pdir); + struct dirent* (*readdir)(DIR* pdir); + }; + union { + int (*readdir_r_p)(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + int (*readdir_r)(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + }; + union { + long (*telldir_p)(void* ctx, DIR* pdir); + long (*telldir)(DIR* pdir); + }; + union { + void (*seekdir_p)(void* ctx, DIR* pdir, long offset); + void (*seekdir)(DIR* pdir, long offset); + }; + union { + int (*closedir_p)(void* ctx, DIR* pdir); + int (*closedir)(DIR* pdir); + }; + union { + int (*mkdir_p)(void* ctx, const char* name, mode_t mode); + int (*mkdir)(const char* name, mode_t mode); + }; + union { + int (*rmdir_p)(void* ctx, const char* name); + int (*rmdir)(const char* name); + }; + union { + int (*fcntl_p)(void* ctx, int fd, int cmd, va_list args); + int (*fcntl)(int fd, int cmd, va_list args); + }; + union { + int (*ioctl_p)(void* ctx, int fd, int cmd, va_list args); + int (*ioctl)(int fd, int cmd, va_list args); + }; + union { + int (*fsync_p)(void* ctx, int fd); + int (*fsync)(int fd); + }; + union { + int (*access_p)(void* ctx, const char *path, int amode); + int (*access)(const char *path, int amode); + }; + union { + 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 */ + int (*socket_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout); + /** called by VFS to interrupt the socket_select call when select is activated from a non-socket VFS driver; set only for the socket driver */ + void (*stop_socket_select)(); + /** stop_socket_select which can be called from ISR; set only for the socket driver */ + void (*stop_socket_select_isr)(BaseType_t *woken); + /** end_select is called to stop the I/O multiplexing and deinitialize the environment created by start_select for the given VFS */ + void (*end_select)(); +} esp_vfs_t; + + +/** + * Register a virtual filesystem for given path prefix. + * + * @param base_path file path prefix associated with the filesystem. + * Must be a zero-terminated C string, up to ESP_VFS_PATH_MAX + * characters long, and at least 2 characters long. + * Name must start with a "/" and must not end with "/". + * For example, "/data" or "/dev/spi" are valid. + * These VFSes would then be called to handle file paths such as + * "/data/myfile.txt" or "/dev/spi/0". + * @param vfs Pointer to esp_vfs_t, a structure which maps syscalls to + * the filesystem driver functions. VFS component doesn't + * assume ownership of this pointer. + * @param ctx If vfs->flags has ESP_VFS_FLAG_CONTEXT_PTR set, a pointer + * which should be passed to VFS functions. Otherwise, NULL. + * + * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are + * registered. + */ +esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx); + + +/** + * Special case function for registering a VFS that uses a method other than + * open() to open new file descriptors from the interval +#include + +/** + * This header file provides POSIX-compatible definitions of directory + * access functions and related data types. + * See http://pubs.opengroup.org/onlinepubs/7908799/xsh/dirent.h.html + * for reference. + */ + +/** + * @brief Opaque directory structure + */ +typedef struct { + uint16_t dd_vfs_idx; /*!< VFS index, not to be used by applications */ + uint16_t dd_rsv; /*!< field reserved for future extension */ + /* remaining fields are defined by VFS implementation */ +} DIR; + +/** + * @brief Directory entry structure + */ +struct dirent { + int d_ino; /*!< file number */ + uint8_t d_type; /*!< not defined in POSIX, but present in BSD and Linux */ +#define DT_UNKNOWN 0 +#define DT_REG 1 +#define DT_DIR 2 + char d_name[256]; /*!< zero-terminated file name */ +}; + +DIR* opendir(const char* name); +struct dirent* readdir(DIR* pdir); +long telldir(DIR* pdir); +void seekdir(DIR* pdir, long loc); +void rewinddir(DIR* pdir); +int closedir(DIR* pdir); +int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + diff --git a/components/vfs/include/sys/ioctl.h b/components/vfs/include/sys/ioctl.h index 4b8e4390..90cbb47d 100644 --- a/components/vfs/include/sys/ioctl.h +++ b/components/vfs/include/sys/ioctl.h @@ -18,10 +18,6 @@ extern "C" { #endif -#include - -#undef ioctl - int ioctl(int fd, int request, ...); #ifdef __cplusplus diff --git a/components/vfs/src/ioctl.c b/components/vfs/src/ioctl.c deleted file mode 100644 index c5766c53..00000000 --- a/components/vfs/src/ioctl.c +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018-2019 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 -#include - -int ioctl(int fd, int request, ...) -{ - int ret; - void *p; - va_list va; - - va_start(va, request); - - p = va_arg(va, void *); - ret = lwip_ioctl(fd, request, p); - - va_end(va); - - return ret; -} - diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c new file mode 100644 index 00000000..e59795cc --- /dev/null +++ b/components/vfs/vfs.c @@ -0,0 +1,1098 @@ +// 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. +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_vfs.h" +#include "sdkconfig.h" + +#ifdef CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT +#define LOG_LOCAL_LEVEL ESP_LOG_NONE +#endif //CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT +#include "esp_log.h" + +static const char *TAG = "vfs"; + +#define VFS_MAX_COUNT 8 /* max number of VFS entries (registered filesystems) */ +#define LEN_PATH_PREFIX_IGNORED SIZE_MAX /* special length value for VFS which is never recognised by open() */ +#define FD_TABLE_ENTRY_UNUSED (fd_table_t) { .permanent = false, .vfs_index = -1, .local_fd = -1 } + +typedef uint8_t local_fd_t; +_Static_assert((1 << (sizeof(local_fd_t)*8)) >= MAX_FDS, "file descriptor type too small"); + +typedef int8_t vfs_index_t; +_Static_assert((1 << (sizeof(vfs_index_t)*8)) >= VFS_MAX_COUNT, "VFS index type too small"); +_Static_assert(((vfs_index_t) -1) < 0, "vfs_index_t must be a signed type"); + +typedef struct { + bool permanent; + vfs_index_t vfs_index; + local_fd_t local_fd; +} fd_table_t; + +typedef struct vfs_entry_ { + esp_vfs_t vfs; // contains pointers to VFS functions + char path_prefix[ESP_VFS_PATH_MAX]; // path prefix mapped to this VFS + size_t path_prefix_len; // micro-optimization to avoid doing extra strlen + void* ctx; // optional pointer which can be passed to VFS + int offset; // index of this structure in s_vfs array +} vfs_entry_t; + +typedef struct { + bool isset; // none or at least one bit is set in the following 3 fd sets + fd_set readfds; + fd_set writefds; + fd_set errorfds; +} fds_triple_t; + +static vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 }; +static size_t s_vfs_count = 0; + +static fd_table_t s_fd_table[MAX_FDS] = { [0 ... MAX_FDS-1] = FD_TABLE_ENTRY_UNUSED }; +static _lock_t s_fd_table_lock; + +static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *vfs_index) +{ + if (len != LEN_PATH_PREFIX_IGNORED) { + if ((len != 0 && len < 2) || (len > ESP_VFS_PATH_MAX)) { + return ESP_ERR_INVALID_ARG; + } + if ((len > 0 && base_path[0] != '/') || base_path[len - 1] == '/') { + return ESP_ERR_INVALID_ARG; + } + } + vfs_entry_t *entry = (vfs_entry_t*) malloc(sizeof(vfs_entry_t)); + if (entry == NULL) { + return ESP_ERR_NO_MEM; + } + size_t index; + for (index = 0; index < s_vfs_count; ++index) { + if (s_vfs[index] == NULL) { + break; + } + } + if (index == s_vfs_count) { + if (s_vfs_count >= VFS_MAX_COUNT) { + free(entry); + return ESP_ERR_NO_MEM; + } + ++s_vfs_count; + } + s_vfs[index] = entry; + if (len != LEN_PATH_PREFIX_IGNORED) { + strcpy(entry->path_prefix, base_path); // we have already verified argument length + } else { + bzero(entry->path_prefix, sizeof(entry->path_prefix)); + } + memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t)); + entry->path_prefix_len = len; + entry->ctx = ctx; + entry->offset = index; + + if (vfs_index) { + *vfs_index = index; + } + + return ESP_OK; +} + +esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx) +{ + return esp_vfs_register_common(base_path, strlen(base_path), vfs, ctx, NULL); +} + +esp_err_t esp_vfs_register_fd_range(const esp_vfs_t *vfs, void *ctx, int min_fd, int max_fd) +{ + if (min_fd < 0 || max_fd < 0 || min_fd > MAX_FDS || max_fd > MAX_FDS || min_fd > max_fd) { + ESP_LOGD(TAG, "Invalid arguments: esp_vfs_register_fd_range(0x%x, 0x%x, %d, %d)", (int) vfs, (int) ctx, min_fd, max_fd); + return ESP_ERR_INVALID_ARG; + } + + int index = -1; + esp_err_t ret = esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, &index); + + if (ret == ESP_OK) { + _lock_acquire(&s_fd_table_lock); + for (int i = min_fd; i < max_fd; ++i) { + if (s_fd_table[i].vfs_index != -1) { + free(s_vfs[i]); + s_vfs[i] = NULL; + for (int j = min_fd; j < i; ++j) { + if (s_fd_table[j].vfs_index == index) { + s_fd_table[j] = FD_TABLE_ENTRY_UNUSED; + } + } + _lock_release(&s_fd_table_lock); + ESP_LOGD(TAG, "esp_vfs_register_fd_range cannot set fd %d (used by other VFS)", i); + return ESP_ERR_INVALID_ARG; + } + s_fd_table[i].permanent = true; + s_fd_table[i].vfs_index = index; + s_fd_table[i].local_fd = i; + } + _lock_release(&s_fd_table_lock); + } + + ESP_LOGD(TAG, "esp_vfs_register_fd_range is successful for range <%d; %d) and VFS ID %d", min_fd, max_fd, index); + + return ret; +} + +esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t *vfs_id) +{ + if (vfs_id == NULL) { + return ESP_ERR_INVALID_ARG; + } + + *vfs_id = -1; + return esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, vfs_id); +} + +esp_err_t esp_vfs_unregister(const char* base_path) +{ + const size_t base_path_len = strlen(base_path); + for (size_t i = 0; i < s_vfs_count; ++i) { + vfs_entry_t* vfs = s_vfs[i]; + if (vfs == NULL) { + continue; + } + if (base_path_len == vfs->path_prefix_len && + memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) { + free(vfs); + s_vfs[i] = NULL; + + _lock_acquire(&s_fd_table_lock); + // Delete all references from the FD lookup-table + for (int j = 0; j < MAX_FDS; ++j) { + if (s_fd_table[j].vfs_index == i) { + s_fd_table[j] = FD_TABLE_ENTRY_UNUSED; + } + } + _lock_release(&s_fd_table_lock); + + return ESP_OK; + } + } + return ESP_ERR_INVALID_STATE; +} + +esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd) +{ + if (vfs_id < 0 || vfs_id >= s_vfs_count || fd == NULL) { + ESP_LOGD(TAG, "Invalid arguments for esp_vfs_register_fd(%d, 0x%x)", vfs_id, (int) fd); + return ESP_ERR_INVALID_ARG; + } + + esp_err_t ret = ESP_ERR_NO_MEM; + _lock_acquire(&s_fd_table_lock); + for (int i = 0; i < MAX_FDS; ++i) { + if (s_fd_table[i].vfs_index == -1) { + s_fd_table[i].permanent = true; + s_fd_table[i].vfs_index = vfs_id; + s_fd_table[i].local_fd = i; + *fd = i; + ret = ESP_OK; + break; + } + } + _lock_release(&s_fd_table_lock); + + ESP_LOGD(TAG, "esp_vfs_register_fd(%d, 0x%x) finished with %s", vfs_id, (int) fd, esp_err_to_name(ret)); + + return ret; +} + +esp_err_t esp_vfs_unregister_fd(esp_vfs_id_t vfs_id, int fd) +{ + esp_err_t ret = ESP_ERR_INVALID_ARG; + + if (vfs_id < 0 || vfs_id >= s_vfs_count || fd < 0 || fd >= MAX_FDS) { + ESP_LOGD(TAG, "Invalid arguments for esp_vfs_unregister_fd(%d, %d)", vfs_id, fd); + return ret; + } + + _lock_acquire(&s_fd_table_lock); + fd_table_t *item = s_fd_table + fd; + if (item->permanent == true && item->vfs_index == vfs_id && item->local_fd == fd) { + *item = FD_TABLE_ENTRY_UNUSED; + ret = ESP_OK; + } + _lock_release(&s_fd_table_lock); + + ESP_LOGD(TAG, "esp_vfs_unregister_fd(%d, %d) finished with %s", vfs_id, fd, esp_err_to_name(ret)); + + return ret; +} + +static inline const vfs_entry_t *get_vfs_for_index(int index) +{ + if (index < 0 || index >= s_vfs_count) { + return NULL; + } else { + return s_vfs[index]; + } +} + +static inline bool fd_valid(int fd) +{ + return (fd < MAX_FDS) && (fd >= 0); +} + +static const vfs_entry_t *get_vfs_for_fd(int fd) +{ + const vfs_entry_t *vfs = NULL; + if (fd_valid(fd)) { + const int index = s_fd_table[fd].vfs_index; // single read -> no locking is required + vfs = get_vfs_for_index(index); + } + return vfs; +} + +static inline int get_local_fd(const vfs_entry_t *vfs, int fd) +{ + int local_fd = -1; + + if (vfs && fd_valid(fd)) { + local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required + } + + return local_fd; +} + +static const char* translate_path(const vfs_entry_t* vfs, const char* src_path) +{ + assert(strncmp(src_path, vfs->path_prefix, vfs->path_prefix_len) == 0); + if (strlen(src_path) == vfs->path_prefix_len) { + // special case when src_path matches the path prefix exactly + return "/"; + } + return src_path + vfs->path_prefix_len; +} + +static const vfs_entry_t* get_vfs_for_path(const char* path) +{ + const vfs_entry_t* best_match = NULL; + ssize_t best_match_prefix_len = -1; + size_t len = strlen(path); + for (size_t i = 0; i < s_vfs_count; ++i) { + const vfs_entry_t* vfs = s_vfs[i]; + if (!vfs || vfs->path_prefix_len == LEN_PATH_PREFIX_IGNORED) { + continue; + } + // match path prefix + if (len < vfs->path_prefix_len || + memcmp(path, vfs->path_prefix, vfs->path_prefix_len) != 0) { + continue; + } + // this is the default VFS and we don't have a better match yet. + if (vfs->path_prefix_len == 0 && !best_match) { + best_match = vfs; + continue; + } + // if path is not equal to the prefix, expect to see a path separator + // i.e. don't match "/data" prefix for "/data1/foo.txt" path + if (len > vfs->path_prefix_len && + path[vfs->path_prefix_len] != '/') { + continue; + } + // Out of all matching path prefixes, select the longest one; + // i.e. if "/dev" and "/dev/uart" both match, for "/dev/uart/1" path, + // choose "/dev/uart", + // This causes all s_vfs_count VFS entries to be scanned when opening + // a file by name. This can be optimized by introducing a table for + // FS search order, sorted so that longer prefixes are checked first. + if (best_match_prefix_len < (ssize_t) vfs->path_prefix_len) { + best_match_prefix_len = (ssize_t) vfs->path_prefix_len; + best_match = vfs; + } + } + return best_match; +} + +/* + * Using huge multi-line macros is never nice, but in this case + * the only alternative is to repeat this chunk of code (with different function names) + * for each syscall being implemented. Given that this define is contained within a single + * file, this looks like a good tradeoff. + * + * First we check if syscall is implemented by VFS (corresponding member is not NULL), + * then call the right flavor of the method (e.g. open or open_p) depending on + * ESP_VFS_FLAG_CONTEXT_PTR flag. If ESP_VFS_FLAG_CONTEXT_PTR is set, context is passed + * in as first argument and _p variant is used for the call. + * It is enough to check just one of them for NULL, as both variants are part of a union. + */ +#define CHECK_AND_CALL(ret, r, pvfs, func, ...) \ + if (pvfs->vfs.func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return -1; \ + } \ + if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + ret = (*pvfs->vfs.func)(__VA_ARGS__);\ + } + + +#define CHECK_AND_CALLV(r, pvfs, func, ...) \ + if (pvfs->vfs.func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return; \ + } \ + if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + (*pvfs->vfs.func)(__VA_ARGS__);\ + } + +#define CHECK_AND_CALLP(ret, r, pvfs, func, ...) \ + if (pvfs->vfs.func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return NULL; \ + } \ + if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + ret = (*pvfs->vfs.func)(__VA_ARGS__);\ + } + +int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) +{ + const vfs_entry_t *vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char *path_within_vfs = translate_path(vfs, path); + int fd_within_vfs; + CHECK_AND_CALL(fd_within_vfs, r, vfs, open, path_within_vfs, flags, mode); + if (fd_within_vfs >= 0) { + _lock_acquire(&s_fd_table_lock); + for (int i = 0; i < MAX_FDS; ++i) { + if (s_fd_table[i].vfs_index == -1) { + s_fd_table[i].permanent = false; + s_fd_table[i].vfs_index = vfs->offset; + s_fd_table[i].local_fd = fd_within_vfs; + _lock_release(&s_fd_table_lock); + return i; + } + } + _lock_release(&s_fd_table_lock); + int ret; + CHECK_AND_CALL(ret, r, vfs, close, fd_within_vfs); + (void) ret; // remove "set but not used" warning + __errno_r(r) = ENOMEM; + return -1; + } + __errno_r(r) = ENOENT; + return -1; +} + +ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + ssize_t ret; + CHECK_AND_CALL(ret, r, vfs, write, local_fd, data, size); + return ret; +} + +off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + off_t ret; + CHECK_AND_CALL(ret, r, vfs, lseek, local_fd, size, mode); + return ret; +} + +ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + ssize_t ret; + CHECK_AND_CALL(ret, r, vfs, read, local_fd, dst, size); + return ret; +} + + +int esp_vfs_close(struct _reent *r, int fd) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, close, local_fd); + + _lock_acquire(&s_fd_table_lock); + if (!s_fd_table[fd].permanent) { + s_fd_table[fd] = FD_TABLE_ENTRY_UNUSED; + } + _lock_release(&s_fd_table_lock); + return ret; +} + +int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + const int local_fd = get_local_fd(vfs, fd); + if (vfs == NULL || local_fd < 0) { + __errno_r(r) = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, fstat, local_fd, st); + return ret; +} + +int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st) +{ + const vfs_entry_t* vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + int ret; + CHECK_AND_CALL(ret, r, vfs, stat, path_within_vfs, st); + return ret; +} + +int esp_vfs_link(struct _reent *r, const char* n1, const char* n2) +{ + const vfs_entry_t* vfs = get_vfs_for_path(n1); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const vfs_entry_t* vfs2 = get_vfs_for_path(n2); + if (vfs != vfs2) { + __errno_r(r) = EXDEV; + return -1; + } + const char* path1_within_vfs = translate_path(vfs, n1); + const char* path2_within_vfs = translate_path(vfs, n2); + int ret; + CHECK_AND_CALL(ret, r, vfs, link, path1_within_vfs, path2_within_vfs); + return ret; +} + +int esp_vfs_unlink(struct _reent *r, const char *path) +{ + const vfs_entry_t* vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + int ret; + CHECK_AND_CALL(ret, r, vfs, unlink, path_within_vfs); + return ret; +} + +int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) +{ + const vfs_entry_t* vfs = get_vfs_for_path(src); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const vfs_entry_t* vfs_dst = get_vfs_for_path(dst); + if (vfs != vfs_dst) { + __errno_r(r) = EXDEV; + return -1; + } + const char* src_within_vfs = translate_path(vfs, src); + const char* dst_within_vfs = translate_path(vfs, dst); + int ret; + CHECK_AND_CALL(ret, r, vfs, rename, src_within_vfs, dst_within_vfs); + return ret; +} + +DIR* opendir(const char* name) +{ + const vfs_entry_t* vfs = get_vfs_for_path(name); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return NULL; + } + const char* path_within_vfs = translate_path(vfs, name); + DIR* ret; + CHECK_AND_CALLP(ret, r, vfs, opendir, path_within_vfs); + if (ret != NULL) { + ret->dd_vfs_idx = vfs->offset; + } + return ret; +} + +struct dirent* readdir(DIR* pdir) +{ + const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return NULL; + } + struct dirent* ret; + CHECK_AND_CALLP(ret, r, vfs, readdir, pdir); + return ret; +} + +int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent) +{ + const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + errno = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, readdir_r, pdir, entry, out_dirent); + return ret; +} + +long telldir(DIR* pdir) +{ + const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + errno = EBADF; + return -1; + } + long ret; + CHECK_AND_CALL(ret, r, vfs, telldir, pdir); + return ret; +} + +void seekdir(DIR* pdir, long loc) +{ + const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + errno = EBADF; + return; + } + CHECK_AND_CALLV(r, vfs, seekdir, pdir, loc); +} + +void rewinddir(DIR* pdir) +{ + seekdir(pdir, 0); +} + +int closedir(DIR* pdir) +{ + const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + errno = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, closedir, pdir); + return ret; +} + +int mkdir(const char* name, mode_t mode) +{ + const vfs_entry_t* vfs = get_vfs_for_path(name); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, name); + int ret; + CHECK_AND_CALL(ret, r, vfs, mkdir, path_within_vfs, mode); + return ret; +} + +int rmdir(const char* name) +{ + const vfs_entry_t* vfs = get_vfs_for_path(name); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, name); + int ret; + CHECK_AND_CALL(ret, r, vfs, rmdir, path_within_vfs); + return ret; +} + +int fcntl(int fd, int cmd, ...) +{ + 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; + va_list args; + va_start(args, cmd); + CHECK_AND_CALL(ret, r, vfs, fcntl, local_fd, cmd, args); + va_end(args); + return ret; +} + +int ioctl(int fd, int cmd, ...) +{ + 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; + va_list args; + va_start(args, cmd); + CHECK_AND_CALL(ret, r, vfs, ioctl, local_fd, cmd, args); + va_end(args); + return ret; +} + +int fsync(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, fsync, local_fd); + return ret; +} + +int access(const char *path, int amode) +{ + int ret; + const vfs_entry_t* vfs = get_vfs_for_path(path); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + CHECK_AND_CALL(ret, r, vfs, access, path_within_vfs, amode); + return ret; +} + +int truncate(const char *path, off_t length) +{ + int ret; + const vfs_entry_t* vfs = get_vfs_for_path(path); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + CHECK_AND_CALL(ret, r, vfs, truncate, path_within_vfs, length); + return ret; +} + +static void call_end_selects(int end_index, const fds_triple_t *vfs_fds_triple) +{ + for (int i = 0; i < end_index; ++i) { + const vfs_entry_t *vfs = get_vfs_for_index(i); + const fds_triple_t *item = &vfs_fds_triple[i]; + if (vfs && vfs->vfs.end_select && item->isset) { + vfs->vfs.end_select(); + } + } +} + +static inline bool esp_vfs_safe_fd_isset(int fd, const fd_set *fds) +{ + return fds && FD_ISSET(fd, fds); +} + +static int set_global_fd_sets(const fds_triple_t *vfs_fds_triple, int size, fd_set *readfds, fd_set *writefds, fd_set *errorfds) +{ + int ret = 0; + + for (int i = 0; i < size; ++i) { + const fds_triple_t *item = &vfs_fds_triple[i]; + if (item->isset) { + for (int fd = 0; fd < MAX_FDS; ++fd) { + const int local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required + if (readfds && esp_vfs_safe_fd_isset(local_fd, &item->readfds)) { + ESP_LOGD(TAG, "FD %d in readfds was set from VFS ID %d", fd, i); + FD_SET(fd, readfds); + ++ret; + } + if (writefds && esp_vfs_safe_fd_isset(local_fd, &item->writefds)) { + ESP_LOGD(TAG, "FD %d in writefds was set from VFS ID %d", fd, i); + FD_SET(fd, writefds); + ++ret; + } + if (errorfds && esp_vfs_safe_fd_isset(local_fd, &item->errorfds)) { + ESP_LOGD(TAG, "FD %d in errorfds was set from VFS ID %d", fd, i); + FD_SET(fd, errorfds); + ++ret; + } + } + } + } + + return ret; +} + +static void esp_vfs_log_fd_set(const char *fds_name, const fd_set *fds) +{ + if (fds_name && fds) { + ESP_LOGD(TAG, "FDs in %s =", fds_name); + for (int i = 0; i < MAX_FDS; ++i) { + if (esp_vfs_safe_fd_isset(i, fds)) { + ESP_LOGD(TAG, "%d", i); + } + } + } +} + +int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) +{ + int ret = 0; + struct _reent* r = __getreent(); + + ESP_LOGD(TAG, "esp_vfs_select starts with nfds = %d", nfds); + if (timeout) { + ESP_LOGD(TAG, "timeout is %lds + %ldus", timeout->tv_sec, timeout->tv_usec); + } + esp_vfs_log_fd_set("readfds", readfds); + esp_vfs_log_fd_set("writefds", writefds); + esp_vfs_log_fd_set("errorfds", errorfds); + + if (nfds > MAX_FDS || nfds < 0) { + ESP_LOGD(TAG, "incorrect nfds"); + __errno_r(r) = EINVAL; + return -1; + } + + fds_triple_t *vfs_fds_triple; + if ((vfs_fds_triple = calloc(s_vfs_count, sizeof(fds_triple_t))) == NULL) { + __errno_r(r) = ENOMEM; + ESP_LOGD(TAG, "calloc is unsuccessful"); + return -1; + } + + int (*socket_select)(int, fd_set *, fd_set *, fd_set *, struct timeval *) = NULL; + for (int fd = 0; fd < nfds; ++fd) { + _lock_acquire(&s_fd_table_lock); + const bool is_socket_fd = s_fd_table[fd].permanent; + const int vfs_index = s_fd_table[fd].vfs_index; + const int local_fd = s_fd_table[fd].local_fd; + _lock_release(&s_fd_table_lock); + + if (vfs_index < 0) { + continue; + } + + if (is_socket_fd) { + if (!socket_select) { + // no socket_select found yet so take a look + if (esp_vfs_safe_fd_isset(fd, readfds) || + esp_vfs_safe_fd_isset(fd, writefds) || + esp_vfs_safe_fd_isset(fd, errorfds)) { + const vfs_entry_t *vfs = s_vfs[vfs_index]; + socket_select = vfs->vfs.socket_select; + } + } + continue; + } + + fds_triple_t *item = &vfs_fds_triple[vfs_index]; // FD sets for VFS which belongs to fd + if (esp_vfs_safe_fd_isset(fd, readfds)) { + item->isset = true; + FD_SET(local_fd, &item->readfds); + FD_CLR(fd, readfds); + ESP_LOGD(TAG, "removing %d from readfds and adding as local FD %d to fd_set of VFS ID %d", fd, local_fd, vfs_index); + } + if (esp_vfs_safe_fd_isset(fd, writefds)) { + item->isset = true; + FD_SET(local_fd, &item->writefds); + FD_CLR(fd, writefds); + ESP_LOGD(TAG, "removing %d from writefds and adding as local FD %d to fd_set of VFS ID %d", fd, local_fd, vfs_index); + } + if (esp_vfs_safe_fd_isset(fd, errorfds)) { + item->isset = true; + FD_SET(local_fd, &item->errorfds); + FD_CLR(fd, errorfds); + ESP_LOGD(TAG, "removing %d from errorfds and adding as local FD %d to fd_set of VFS ID %d", fd, local_fd, vfs_index); + } + } + + // all non-socket VFSs have their FD sets in vfs_fds_triple + // the global readfds, writefds and errorfds contain only socket FDs (if + // there any) + + /* Semaphore used for waiting select events from other VFS drivers when socket + * select is not used (not registered or socket FDs are not observed by the + * given call of select) + */ + SemaphoreHandle_t select_sem = NULL; + + if (!socket_select) { + // There is no socket VFS registered or select() wasn't called for + // any socket. Therefore, we will use our own signalization. + if ((select_sem = xSemaphoreCreateBinary()) == NULL) { + free(vfs_fds_triple); + __errno_r(r) = ENOMEM; + ESP_LOGD(TAG, "cannot create select_sem"); + return -1; + } + } + + for (int i = 0; i < s_vfs_count; ++i) { + const vfs_entry_t *vfs = get_vfs_for_index(i); + fds_triple_t *item = &vfs_fds_triple[i]; + + if (vfs && vfs->vfs.start_select && item->isset) { + // call start_select for all non-socket VFSs with has at least one FD set in readfds, writefds, or errorfds + // note: it can point to socket VFS but item->isset will be false for that + ESP_LOGD(TAG, "calling start_select for VFS ID %d with the following local FDs", i); + esp_vfs_log_fd_set("readfds", &item->readfds); + esp_vfs_log_fd_set("writefds", &item->writefds); + esp_vfs_log_fd_set("errorfds", &item->errorfds); + esp_err_t err = vfs->vfs.start_select(nfds, &item->readfds, &item->writefds, &item->errorfds, &select_sem); + + if (err != ESP_OK) { + call_end_selects(i, vfs_fds_triple); + (void) set_global_fd_sets(vfs_fds_triple, s_vfs_count, readfds, writefds, errorfds); + if (select_sem) { + vSemaphoreDelete(select_sem); + select_sem = NULL; + } + free(vfs_fds_triple); + __errno_r(r) = EINTR; + ESP_LOGD(TAG, "start_select failed"); + return -1; + } + } + } + + if (socket_select) { + ESP_LOGD(TAG, "calling socket_select with the following FDs"); + esp_vfs_log_fd_set("readfds", readfds); + esp_vfs_log_fd_set("writefds", writefds); + esp_vfs_log_fd_set("errorfds", errorfds); + ret = socket_select(nfds, readfds, writefds, errorfds, timeout); + ESP_LOGD(TAG, "socket_select returned %d and the FDs are the following", ret); + esp_vfs_log_fd_set("readfds", readfds); + esp_vfs_log_fd_set("writefds", writefds); + esp_vfs_log_fd_set("errorfds", errorfds); + } else { + if (readfds) { + FD_ZERO(readfds); + } + if (writefds) { + FD_ZERO(writefds); + } + if (errorfds) { + FD_ZERO(errorfds); + } + + TickType_t ticks_to_wait = portMAX_DELAY; + if (timeout) { + uint32_t timeout_ms = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; + ticks_to_wait = timeout_ms / portTICK_PERIOD_MS; + ESP_LOGD(TAG, "timeout is %dms", timeout_ms); + } + ESP_LOGD(TAG, "waiting without calling socket_select"); + xSemaphoreTake(select_sem, ticks_to_wait); + } + + call_end_selects(s_vfs_count, vfs_fds_triple); // for VFSs for start_select was called before + if (ret >= 0) { + ret += set_global_fd_sets(vfs_fds_triple, s_vfs_count, readfds, writefds, errorfds); + } + if (select_sem) { + vSemaphoreDelete(select_sem); + select_sem = NULL; + } + free(vfs_fds_triple); + + ESP_LOGD(TAG, "esp_vfs_select returns %d", ret); + esp_vfs_log_fd_set("readfds", readfds); + esp_vfs_log_fd_set("writefds", writefds); + esp_vfs_log_fd_set("errorfds", errorfds); + return ret; +} + +void esp_vfs_select_triggered(SemaphoreHandle_t *signal_sem) +{ + if (signal_sem && (*signal_sem)) { + xSemaphoreGive(*signal_sem); + } else { + // Another way would be to go through s_fd_table and find the VFS + // which has a permanent FD. But in order to avoid to lock + // s_fd_table_lock we go through the VFS table. + for (int i = 0; i < s_vfs_count; ++i) { + const vfs_entry_t *vfs = s_vfs[i]; + if (vfs != NULL && vfs->vfs.stop_socket_select != NULL) { + vfs->vfs.stop_socket_select(); + break; + } + } + } +} + +void esp_vfs_select_triggered_isr(SemaphoreHandle_t *signal_sem, BaseType_t *woken) +{ + if (signal_sem && (*signal_sem)) { + xSemaphoreGiveFromISR(*signal_sem, woken); + } else { + // Another way would be to go through s_fd_table and find the VFS + // which has a permanent FD. But in order to avoid to lock + // s_fd_table_lock we go through the VFS table. + for (int i = 0; i < s_vfs_count; ++i) { + const vfs_entry_t *vfs = s_vfs[i]; + if (vfs != NULL && vfs->vfs.stop_socket_select_isr != NULL) { + vfs->vfs.stop_socket_select_isr(woken); + break; + } + } + } +} + +#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 new file mode 100644 index 00000000..13fa1c94 --- /dev/null +++ b/components/vfs/vfs_uart.c @@ -0,0 +1,940 @@ +// 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. +// 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 +#include +#include +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_vfs_dev.h" +#include "esp_attr.h" +#include "soc/uart_struct.h" +#include "driver/uart.h" +#include "sdkconfig.h" +#include "driver/uart_select.h" + +// TODO: make the number of UARTs chip dependent +#define UART_NUM 3 + +// 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_read_locks[UART_NUM]; +static _lock_t s_uart_write_locks[UART_NUM]; +// One-character buffer used for newline conversion code, per UART +static int s_peek_char[UART_NUM] = { NONE, NONE, NONE }; +// Per-UART non-blocking flag. Note: default implementation does not honor this +// flag, all reads are non-blocking. This option becomes effective if UART +// driver is used. +static bool s_non_blocking[UART_NUM]; + +/* Lock ensuring that uart_select is used from only one task at the time */ +static _lock_t s_one_select_lock; + +static SemaphoreHandle_t *_signal_sem = NULL; +static fd_set *_readfds = NULL; +static fd_set *_writefds = NULL; +static fd_set *_errorfds = NULL; +static fd_set *_readfds_orig = NULL; +static fd_set *_writefds_orig = NULL; +static fd_set *_errorfds_orig = NULL; + +// 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[UART_NUM] = { [0 ... UART_NUM-1] = +#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 +}; + +static void uart_end_select(); + +// 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 + int fd = -1; + + if (strcmp(path, "/0") == 0) { + fd = 0; + } else if (strcmp(path, "/1") == 0) { + fd = 1; + } else if (strcmp(path, "/2") == 0) { + fd = 2; + } else { + errno = ENOENT; + return fd; + } + + s_non_blocking[fd] = ((flags & O_NONBLOCK) == O_NONBLOCK); + + return fd; +} + +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 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 timeout = s_non_blocking[fd] ? 0 : portMAX_DELAY; + int n = uart_read_bytes(fd, &c, 1, timeout); + 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; + /* 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_write_locks[fd]); + for (size_t i = 0; i < size; i++) { + 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; + } + } + s_uart_tx_func[fd](fd, c); + } + _lock_release_recursive(&s_uart_write_locks[fd]); + return 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); + char *data_c = (char *) data; + size_t received = 0; + _lock_acquire_recursive(&s_uart_read_locks[fd]); + while (received < size) { + int c = uart_read_char(fd); + if (c == '\r') { + if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CR) { + c = '\n'; + } else if (s_rx_mode[fd] == 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); + } + } + } else if (c == NONE) { + break; + } + data_c[received] = (char) c; + ++received; + if (c == '\n') { + break; + } + } + _lock_release_recursive(&s_uart_read_locks[fd]); + if (received > 0) { + return received; + } + errno = EWOULDBLOCK; + return -1; +} + +static int uart_fstat(int fd, struct stat * st) +{ + assert(fd >=0 && fd < 3); + st->st_mode = S_IFCHR; + return 0; +} + +static int uart_close(int fd) +{ + assert(fd >=0 && fd < 3); + return 0; +} + +static int uart_fcntl(int fd, int cmd, va_list args) +{ + assert(fd >=0 && fd < 3); + int result = 0; + if (cmd == F_GETFL) { + if (s_non_blocking[fd]) { + result |= O_NONBLOCK; + } + } else if (cmd == F_SETFL) { + int arg = va_arg(args, int); + s_non_blocking[fd] = (arg & O_NONBLOCK) != 0; + } else { + // unsupported operation + result = -1; + errno = ENOSYS; + } + return result; +} + +static int uart_access(const char *path, int amode) +{ + int ret = -1; + + if (strcmp(path, "/0") == 0 || strcmp(path, "/1") == 0 || strcmp(path, "/2") == 0) { + if (F_OK == amode) { + ret = 0; //path exists + } else { + if ((((amode & R_OK) == R_OK) || ((amode & W_OK) == W_OK)) && ((amode & X_OK) != X_OK)) { + ret = 0; //path is readable and/or writable but not executable + } else { + errno = EACCES; + } + } + } else { + errno = ENOENT; + } + + return ret; +} + +static void select_notif_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken) +{ + switch (uart_select_notif) { + case UART_SELECT_READ_NOTIF: + if (FD_ISSET(uart_num, _readfds_orig)) { + FD_SET(uart_num, _readfds); + esp_vfs_select_triggered_isr(_signal_sem, task_woken); + } + break; + case UART_SELECT_WRITE_NOTIF: + if (FD_ISSET(uart_num, _writefds_orig)) { + FD_SET(uart_num, _writefds); + esp_vfs_select_triggered_isr(_signal_sem, task_woken); + } + break; + case UART_SELECT_ERROR_NOTIF: + if (FD_ISSET(uart_num, _errorfds_orig)) { + FD_SET(uart_num, _errorfds); + esp_vfs_select_triggered_isr(_signal_sem, task_woken); + } + break; + } +} + +static esp_err_t uart_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, SemaphoreHandle_t *signal_sem) +{ + if (_lock_try_acquire(&s_one_select_lock)) { + return ESP_ERR_INVALID_STATE; + } + + const int max_fds = MIN(nfds, UART_NUM); + + portENTER_CRITICAL(uart_get_selectlock()); + + if (_readfds || _writefds || _errorfds || _readfds_orig || _writefds_orig || _errorfds_orig || _signal_sem) { + portEXIT_CRITICAL(uart_get_selectlock()); + uart_end_select(); + return ESP_ERR_INVALID_STATE; + } + + if ((_readfds_orig = malloc(sizeof(fd_set))) == NULL) { + portEXIT_CRITICAL(uart_get_selectlock()); + uart_end_select(); + return ESP_ERR_NO_MEM; + } + + if ((_writefds_orig = malloc(sizeof(fd_set))) == NULL) { + portEXIT_CRITICAL(uart_get_selectlock()); + uart_end_select(); + return ESP_ERR_NO_MEM; + } + + if ((_errorfds_orig = malloc(sizeof(fd_set))) == NULL) { + portEXIT_CRITICAL(uart_get_selectlock()); + uart_end_select(); + return ESP_ERR_NO_MEM; + } + + //uart_set_select_notif_callback set the callbacks in UART ISR + for (int i = 0; i < max_fds; ++i) { + if (FD_ISSET(i, readfds) || FD_ISSET(i, writefds) || FD_ISSET(i, exceptfds)) { + uart_set_select_notif_callback(i, select_notif_callback); + } + } + + _signal_sem = signal_sem; + + _readfds = readfds; + _writefds = writefds; + _errorfds = exceptfds; + + *_readfds_orig = *readfds; + *_writefds_orig = *writefds; + *_errorfds_orig = *exceptfds; + + FD_ZERO(readfds); + FD_ZERO(writefds); + FD_ZERO(exceptfds); + + for (int i = 0; i < max_fds; ++i) { + if (FD_ISSET(i, _readfds_orig)) { + size_t buffered_size; + if (uart_get_buffered_data_len(i, &buffered_size) == ESP_OK && buffered_size > 0) { + // signalize immediately when data is buffered + FD_SET(i, _readfds); + esp_vfs_select_triggered(_signal_sem); + } + } + } + + portEXIT_CRITICAL(uart_get_selectlock()); + // s_one_select_lock is not released on successfull exit - will be + // released in uart_end_select() + + return ESP_OK; +} + +static void uart_end_select() +{ + portENTER_CRITICAL(uart_get_selectlock()); + for (int i = 0; i < UART_NUM; ++i) { + uart_set_select_notif_callback(i, NULL); + } + + _signal_sem = NULL; + + _readfds = NULL; + _writefds = NULL; + _errorfds = NULL; + + if (_readfds_orig) { + free(_readfds_orig); + _readfds_orig = NULL; + } + + if (_writefds_orig) { + free(_writefds_orig); + _writefds_orig = NULL; + } + + if (_errorfds_orig) { + free(_errorfds_orig); + _errorfds_orig = NULL; + } + portEXIT_CRITICAL(uart_get_selectlock()); + _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; + } + + /* FALLTHRU */ + + 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 = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &uart_write, + .open = &uart_open, + .fstat = &uart_fstat, + .close = &uart_close, + .read = &uart_read, + .fcntl = &uart_fcntl, + .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) +{ + 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) +{ + s_tx_mode = mode; +} + +void esp_vfs_dev_uart_use_nonblocking(int uart_num) +{ + _lock_acquire_recursive(&s_uart_read_locks[uart_num]); + _lock_acquire_recursive(&s_uart_write_locks[uart_num]); + s_uart_tx_func[uart_num] = uart_tx_char; + s_uart_rx_func[uart_num] = uart_rx_char; + _lock_release_recursive(&s_uart_write_locks[uart_num]); + _lock_release_recursive(&s_uart_read_locks[uart_num]); +} + +void esp_vfs_dev_uart_use_driver(int uart_num) +{ + _lock_acquire_recursive(&s_uart_read_locks[uart_num]); + _lock_acquire_recursive(&s_uart_write_locks[uart_num]); + s_uart_tx_func[uart_num] = uart_tx_char_via_driver; + s_uart_rx_func[uart_num] = uart_rx_char_via_driver; + _lock_release_recursive(&s_uart_write_locks[uart_num]); + _lock_release_recursive(&s_uart_read_locks[uart_num]); +} diff --git a/examples/peripherals/uart_select/CMakeLists.txt b/examples/peripherals/uart_select/CMakeLists.txt new file mode 100644 index 00000000..8cf5a7bd --- /dev/null +++ b/examples/peripherals/uart_select/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(uart_select) diff --git a/examples/peripherals/uart_select/Makefile b/examples/peripherals/uart_select/Makefile new file mode 100644 index 00000000..010458f7 --- /dev/null +++ b/examples/peripherals/uart_select/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := uart_select + +include $(IDF_PATH)/make/project.mk diff --git a/examples/peripherals/uart_select/README.md b/examples/peripherals/uart_select/README.md new file mode 100644 index 00000000..966e9864 --- /dev/null +++ b/examples/peripherals/uart_select/README.md @@ -0,0 +1,13 @@ +# UART Select Example + +The UART select example is for demonstrating the use of `select()` for +synchronous I/O multiplexing on the UART interface. The example waits for a +character from UART using `select()` until a blocking read without delay or a +successful non-blocking read is possible. + +Please note that the same result can be achieved by using `uart_read_bytes()` +but the use of `select()` allows to use it together with other virtual +file system (VFS) drivers, e.g. LWIP sockets. For a more comprehensive example +please refer to `system/select`. + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/peripherals/uart_select/main/CMakeLists.txt b/examples/peripherals/uart_select/main/CMakeLists.txt new file mode 100644 index 00000000..4e3ae00d --- /dev/null +++ b/examples/peripherals/uart_select/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "uart_select_example_main.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/peripherals/uart_select/main/component.mk b/examples/peripherals/uart_select/main/component.mk new file mode 100644 index 00000000..44bd2b52 --- /dev/null +++ b/examples/peripherals/uart_select/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/uart_select/main/uart_select_example_main.c b/examples/peripherals/uart_select/main/uart_select_example_main.c new file mode 100644 index 00000000..717042f2 --- /dev/null +++ b/examples/peripherals/uart_select/main/uart_select_example_main.c @@ -0,0 +1,93 @@ +/* UART Select Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_vfs.h" +#include "esp_vfs_dev.h" +#include "driver/uart.h" + +static const char* TAG = "uart_select_example"; + +static void uart_select_task() +{ + 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_0, &uart_config); + uart_driver_install(UART_NUM_0, 2*1024, 0, 0, NULL, 0); + + while (1) { + int fd; + + if ((fd = open("/dev/uart/0", O_RDWR)) == -1) { + ESP_LOGE(TAG, "Cannot open UART"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + continue; + } + + // We have a driver now installed so set up the read/write functions to use driver also. + esp_vfs_dev_uart_use_driver(0); + + while (1) { + int s; + fd_set rfds; + struct timeval tv = { + .tv_sec = 5, + .tv_usec = 0, + }; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + s = select(fd + 1, &rfds, NULL, NULL, &tv); + + if (s < 0) { + ESP_LOGE(TAG, "Select failed: errno %d", errno); + break; + } else if (s == 0) { + ESP_LOGI(TAG, "Timeout has been reached and nothing has been received"); + } else { + if (FD_ISSET(fd, &rfds)) { + char buf; + if (read(fd, &buf, 1) > 0) { + ESP_LOGI(TAG, "Received: %c", buf); + // Note: Only one character was read even the buffer contains more. The other characters will + // be read one-by-one by subsequent calls to select() which will then return immediately + // without timeout. + } else { + ESP_LOGE(TAG, "UART read error"); + break; + } + } else { + ESP_LOGE(TAG, "No FD has been set in select()"); + break; + } + } + } + + close(fd); + } + + vTaskDelete(NULL); +} + +void app_main() +{ + xTaskCreate(uart_select_task, "uart_select_task", 4*1024, NULL, 5, NULL); +}