mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-05-18 15:46:56 +08:00
Merge branch 'feature/update_vfs_for_fatfs' into 'master'
feat(vfs): update vfs for ESP8266 See merge request sdk/ESP8266_RTOS_SDK!1317
This commit is contained in:
@ -667,16 +667,12 @@ static void uart_rx_intr_handler_default(void *param)
|
||||
notify = UART_SELECT_ERROR_NOTIF;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_USING_ESP_VFS
|
||||
if (uart_event.type != UART_EVENT_MAX && p_uart->uart_select_notif_callback) {
|
||||
p_uart->uart_select_notif_callback(uart_num, notify, &task_woken);
|
||||
if (task_woken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)notify;
|
||||
#endif
|
||||
|
||||
if (uart_event.type != UART_EVENT_MAX && p_uart->xQueueUart) {
|
||||
if (pdFALSE == xQueueSendFromISR(p_uart->xQueueUart, (void *)&uart_event, &task_woken)) {
|
||||
@ -1094,3 +1090,8 @@ esp_err_t uart_set_rx_timeout(uart_port_t uart_num, const uint8_t tout_thresh)
|
||||
UART_EXIT_CRITICAL();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool uart_is_driver_installed(uart_port_t uart_num)
|
||||
{
|
||||
return uart_num < UART_NUM_MAX && (p_uart_obj[uart_num] != NULL);
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/queue.h"
|
||||
@ -553,6 +554,17 @@ esp_err_t uart_get_buffered_data_len(uart_port_t uart_num, size_t *size);
|
||||
*/
|
||||
esp_err_t uart_set_rx_timeout(uart_port_t uart_num, const uint8_t tout_thresh);
|
||||
|
||||
/**
|
||||
* @brief Checks whether the driver is installed or not
|
||||
*
|
||||
* @param uart_num UART port number, the max port number is (UART_NUM_MAX -1).
|
||||
*
|
||||
* @return
|
||||
* - true driver is installed
|
||||
* - false driver is not installed
|
||||
*/
|
||||
bool uart_is_driver_installed(uart_port_t uart_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -21,28 +21,35 @@
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp8266/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()
|
||||
static void lwip_stop_socket_select(void *sem)
|
||||
{
|
||||
sys_sem_signal(sys_thread_sem_get()); //socket_select will return
|
||||
sys_sem_signal(sem); //socket_select will return
|
||||
}
|
||||
|
||||
static void lwip_stop_socket_select_isr(BaseType_t *woken)
|
||||
static void lwip_stop_socket_select_isr(void *sem, BaseType_t *woken)
|
||||
{
|
||||
if (sys_sem_signal_isr(sys_thread_sem_get()) && woken) {
|
||||
if (sys_sem_signal_isr(sem) && woken) {
|
||||
*woken = pdTRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static int lwip_fcntl_r_wrapper(int fd, int cmd, va_list args)
|
||||
static void *lwip_get_socket_select_semaphore(void)
|
||||
{
|
||||
return lwip_fcntl(fd, cmd, va_arg(args, int));
|
||||
/* Calling this from the same process as select() will ensure that the semaphore won't be allocated from
|
||||
* ISR (lwip_stop_socket_select_isr).
|
||||
*/
|
||||
return (void *) sys_thread_sem_get();
|
||||
}
|
||||
|
||||
static int lwip_fcntl_r_wrapper(int fd, int cmd, int arg)
|
||||
{
|
||||
return lwip_fcntl(fd, cmd, arg);
|
||||
}
|
||||
|
||||
static int lwip_ioctl_r_wrapper(int fd, int cmd, va_list args)
|
||||
@ -62,6 +69,7 @@ void esp_vfs_lwip_sockets_register(void)
|
||||
.fcntl = &lwip_fcntl_r_wrapper,
|
||||
.ioctl = &lwip_ioctl_r_wrapper,
|
||||
.socket_select = &lwip_select,
|
||||
.get_socket_select_semaphore = &lwip_get_socket_select_semaphore,
|
||||
.stop_socket_select = &lwip_stop_socket_select,
|
||||
.stop_socket_select_isr = &lwip_stop_socket_select_isr,
|
||||
};
|
||||
|
@ -18,9 +18,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#ifdef CONFIG_USING_ESP_VFS
|
||||
#include "esp_vfs_dev.h"
|
||||
#endif
|
||||
|
||||
#define _STR(_s) #_s
|
||||
#define STR(_s) _STR(_s)
|
||||
@ -56,9 +54,7 @@ int esp_newlib_init(void)
|
||||
|
||||
esp_reent_init(_global_impure_ptr);
|
||||
|
||||
#ifdef CONFIG_USING_ESP_VFS
|
||||
esp_vfs_dev_uart_register();
|
||||
#endif
|
||||
|
||||
_GLOBAL_REENT->_stdout = fopen(default_uart_dev, "w");
|
||||
if (!_GLOBAL_REENT->_stdout)
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include <sys/select.h>
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_USING_ESP_VFS
|
||||
#include "esp_vfs.h"
|
||||
|
||||
#ifdef CONFIG_USE_ONLY_LWIP_SELECT
|
||||
@ -64,4 +63,3 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct
|
||||
return esp_vfs_select(nfds, readfds, writefds, errorfds, timeout);
|
||||
#endif
|
||||
}
|
||||
#endif /* CONFIG_USING_ESP_VFS */
|
||||
|
@ -16,119 +16,17 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <reent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/fcntl.h>
|
||||
|
||||
#include "esp_libc.h"
|
||||
#include "FreeRTOS.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#ifdef CONFIG_USING_ESP_VFS
|
||||
|
||||
#include "esp_vfs.h"
|
||||
|
||||
int _open_r(struct _reent *r, const char *filename, int flags, int mode)
|
||||
{
|
||||
return esp_vfs_open(r, filename, flags, mode);
|
||||
}
|
||||
|
||||
_ssize_t _read_r(struct _reent *r, int fd, void *buf, size_t len)
|
||||
{
|
||||
return esp_vfs_read(r, fd, buf, len);
|
||||
}
|
||||
|
||||
_ssize_t _write_r(struct _reent *r, int fd, const void *buf, size_t len)
|
||||
{
|
||||
return esp_vfs_write(r, fd, buf, len);
|
||||
}
|
||||
|
||||
_off_t _lseek_r(struct _reent *r, int fd, _off_t where, int whence)
|
||||
{
|
||||
return esp_vfs_lseek(r, fd, where, whence);
|
||||
}
|
||||
|
||||
int _close_r(struct _reent *r, int fd)
|
||||
{
|
||||
return esp_vfs_close(r, fd);
|
||||
}
|
||||
|
||||
int _rename_r(struct _reent *r, const char *from, const char *to)
|
||||
{
|
||||
return esp_vfs_rename(r, from, to);
|
||||
}
|
||||
|
||||
int _unlink_r(struct _reent *r, const char *filename)
|
||||
{
|
||||
return esp_vfs_unlink(r, filename);
|
||||
}
|
||||
|
||||
int _fstat_r(struct _reent *r, int fd, struct stat *s)
|
||||
{
|
||||
return esp_vfs_fstat(r, fd, s);
|
||||
}
|
||||
|
||||
int _stat_r(struct _reent *r, const char *path, struct stat *st)
|
||||
{
|
||||
return esp_vfs_stat(r, path, st);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int _open_r(struct _reent *r, const char *filename, int flags, int mode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
_ssize_t _read_r(struct _reent *r, int fd, void *buf, size_t len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
_ssize_t _write_r(struct _reent *r, int fd, const void *buf, size_t len)
|
||||
{
|
||||
int i;
|
||||
const char *cbuf = buf;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
ets_putc(cbuf[i]);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
_off_t _lseek_r(struct _reent *r, int fd, _off_t where, int whence)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _close_r(struct _reent *r, int fd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _rename_r(struct _reent *r, const char *from, const char *to)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _unlink_r(struct _reent *r, const char *filename)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _fstat_r(struct _reent *r, int fd, struct stat *s)
|
||||
{
|
||||
s->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _stat_r(struct _reent *r, const char *path, struct stat *st)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_USING_ESP_VFS */
|
||||
|
||||
void *_malloc_r(struct _reent *r, size_t n)
|
||||
{
|
||||
void *return_addr = (void *)__builtin_return_address(0);
|
||||
@ -184,3 +82,14 @@ int _getpid_r(struct _reent *r)
|
||||
__errno_r(r) = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Replaces newlib fcntl, which has been compiled without HAVE_FCNTL */
|
||||
int fcntl(int fd, int cmd, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, cmd);
|
||||
int arg = va_arg(args, int);
|
||||
va_end(args);
|
||||
struct _reent* r = __getreent();
|
||||
return _fcntl_r(r, fd, cmd, arg);
|
||||
}
|
||||
|
@ -1,13 +1,8 @@
|
||||
idf_component_register(SRCS "vfs.c"
|
||||
"vfs_uart.c"
|
||||
"vfs_semihost.c"
|
||||
INCLUDE_DIRS include)
|
||||
|
||||
if(CONFIG_USING_ESP_VFS)
|
||||
set(COMPONENT_SRCS "vfs.c"
|
||||
"vfs_uart.c")
|
||||
else()
|
||||
set(COMPONENT_SRCDIRS "port")
|
||||
endif()
|
||||
|
||||
set(COMPONENT_ADD_INCLUDEDIRS "include")
|
||||
|
||||
set(COMPONENT_REQUIRES "lwip")
|
||||
|
||||
register_component()
|
||||
# Some newlib syscalls are implemented in vfs.c, make sure these are always
|
||||
# seen by the linker
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u vfs_include_syscalls_impl")
|
||||
|
@ -1,27 +1,34 @@
|
||||
menu "Virtual file system"
|
||||
|
||||
config USING_ESP_VFS
|
||||
bool "Using espressif VFS"
|
||||
default y
|
||||
help
|
||||
Enable this option, espressif VFS can be used. Users can use APIs like "open", "read", "write"
|
||||
and so on to operate I/O device which is registered.
|
||||
config VFS_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 SUPPRESS_SELECT_DEBUG_OUTPUT
|
||||
bool "Suppress select() related debug outputs"
|
||||
default n
|
||||
depends on USING_ESP_VFS
|
||||
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 VFS_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.
|
||||
|
||||
config SUPPORT_TERMIOS
|
||||
bool "Add support for termios.h"
|
||||
default n
|
||||
depends on USING_ESP_VFS
|
||||
help
|
||||
Disabling this option can save memory when the support for termios.h is not required.
|
||||
menu "Host File System I/O (Semihosting)"
|
||||
config SEMIHOSTFS_MAX_MOUNT_POINTS
|
||||
int "Maximum number of the host filesystem mount points"
|
||||
default 1
|
||||
help
|
||||
Define maximum number of host filesystem mount points.
|
||||
|
||||
config SEMIHOSTFS_HOST_PATH_MAX_LEN
|
||||
int "Maximum path length for the host base directory"
|
||||
default 128
|
||||
help
|
||||
Define maximum path length for the host base directory which is to be mounted.
|
||||
If host path passed to esp_vfs_semihost_register() is longer than this value
|
||||
it will be truncated.
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
|
@ -1,21 +1,22 @@
|
||||
Virtual filesystem component
|
||||
============================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
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.
|
||||
Virtual filesystem (VFS) component provides a unified interface for drivers which can perform operations on file-like objects. These can be real filesystems (FAT, SPIFFS, etc.) or device drivers which provide a 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.
|
||||
This component allows C library functions, such as fopen and fprintf, to work with FS drivers. At a high level, each FS driver is associated with some path prefix. When one of C library functions needs to open a file, the VFS component searches for the FS driver associated with the file 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 the ``/fat`` prefix and call ``fopen("/fat/file.txt", "w")``. The VFS component will then call the function ``open`` of the FAT driver and pass the argument ``/file.txt`` to it together with appropriate mode flags. All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT 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:
|
||||
To register an FS driver, an application needs to define an instance of the :cpp:type:`esp_vfs_t` structure and populate it with function pointers to FS APIs:
|
||||
|
||||
.. highlight:: c
|
||||
|
||||
@ -32,9 +33,9 @@ To register an FS driver, application needs to define in instance of :cpp:type:`
|
||||
|
||||
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.
|
||||
Depending on the way how the FS driver declares its API functions, 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)::
|
||||
Case 1: API functions are declared without an extra context pointer (the FS driver is a singleton)::
|
||||
|
||||
ssize_t myfs_write(int fd, const void * data, size_t size);
|
||||
|
||||
@ -46,7 +47,7 @@ Case 1: API functions are declared without an extra context pointer (FS driver i
|
||||
// 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)::
|
||||
Case 2: API functions are declared with an extra context pointer (the FS driver supports multiple instances)::
|
||||
|
||||
ssize_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size);
|
||||
|
||||
@ -67,9 +68,29 @@ Case 2: API functions are declared with an extra context pointer (FS driver supp
|
||||
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:
|
||||
Synchronous input/output multiplexing by :cpp:func:`select` is supported in the VFS component. The implementation
|
||||
works in the following way.
|
||||
|
||||
1. :cpp:func:`select` is called with file descriptors which could belong to various VFS drivers.
|
||||
2. The file descriptors are divided into groups each belonging to one VFS driver.
|
||||
3. The file descriptors belonging to non-socket VFS drivers are handed over to the given VFS drivers by :cpp:func:`start_select`
|
||||
described later on this page. This function represents the driver-specific implementation of :cpp:func:`select` for
|
||||
the given driver. This should be a non-blocking call which means the function should immediately return after setting up
|
||||
the environment for checking events related to the given file descriptors.
|
||||
4. The file descriptors belonging to the socket VFS driver are handed over to the socket driver by
|
||||
:cpp:func:`socket_select` described later on this page. This is a blocking call which means that it will return only
|
||||
if there is an event related to socket file descriptors or a non-socket driver signals :cpp:func:`socket_select`
|
||||
to exit.
|
||||
5. Results are collected from each VFS driver and all drivers are stopped by deinitiazation
|
||||
of the environment for checking events.
|
||||
6. The :cpp:func:`select` call ends and returns the appropriate results.
|
||||
|
||||
Non-socket VFS drivers
|
||||
""""""""""""""""""""""
|
||||
|
||||
If you want to use :cpp:func:`select` with a file descriptor belonging to a non-socket VFS driver
|
||||
then you need to register the driver with functions :cpp:func:`start_select` and
|
||||
:cpp:func:`end_select` similarly to the following example:
|
||||
|
||||
.. highlight:: c
|
||||
|
||||
@ -82,25 +103,70 @@ then you need to register the VFS with :cpp:func:`start_select` and
|
||||
|
||||
: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
|
||||
given VFS driver.
|
||||
|
||||
:cpp:func:`end_select` is called to stop/deinitialize/free the
|
||||
environment which was setup by :cpp:func:`start_select`.
|
||||
|
||||
.. note::
|
||||
:cpp:func:`end_select` might be called without a previous :cpp:func:`start_select` call in some rare
|
||||
circumstances. :cpp:func:`end_select` should fail gracefully if this is the case.
|
||||
|
||||
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`.
|
||||
:component_file:`vfs/vfs_uart.c` and most particularly to the functions
|
||||
:cpp:func:`esp_vfs_dev_uart_register`, :cpp:func:`uart_start_select`, and
|
||||
:cpp:func:`uart_end_select` for more information.
|
||||
|
||||
Examples demonstrating the use of :cpp:func:`select` with VFS file descriptors
|
||||
are the :example:`peripherals/uart_select` and the :example:`system/select`
|
||||
examples.
|
||||
Please check the following examples that demonstrate the use of :cpp:func:`select` with VFS file descriptors:
|
||||
- :example:`peripherals/uart/uart_select`
|
||||
- :example:`system/select`
|
||||
|
||||
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.
|
||||
Socket VFS drivers
|
||||
""""""""""""""""""
|
||||
|
||||
A socket VFS driver is using its own internal implementation of :cpp:func:`select` and non-socket VFS drivers notify
|
||||
it upon read/write/error conditions.
|
||||
|
||||
A socket VFS driver needs to be registered with the following functions defined:
|
||||
|
||||
.. highlight:: c
|
||||
|
||||
::
|
||||
|
||||
// In definition of esp_vfs_t:
|
||||
.socket_select = &lwip_select,
|
||||
.get_socket_select_semaphore = &lwip_get_socket_select_semaphore,
|
||||
.stop_socket_select = &lwip_stop_socket_select,
|
||||
.stop_socket_select_isr = &lwip_stop_socket_select_isr,
|
||||
// ... other members initialized
|
||||
|
||||
:cpp:func:`socket_select` is the internal implementation of :cpp:func:`select` for the socket driver. It works only
|
||||
with file descriptors belonging to the socket VFS.
|
||||
|
||||
:cpp:func:`get_socket_select_semaphore` returns the signalization object (semaphore) which will be used in non-socket
|
||||
drivers to stop the waiting in :cpp:func:`socket_select`.
|
||||
|
||||
:cpp:func:`stop_socket_select` call is used to stop the waiting in :cpp:func:`socket_select` by passing the object
|
||||
returned by :cpp:func:`get_socket_select_semaphore`.
|
||||
|
||||
:cpp:func:`stop_socket_select_isr` has the same functionality as :cpp:func:`stop_socket_select` but it can be used
|
||||
from ISR.
|
||||
|
||||
Please see :component_file:`lwip/port/esp32/vfs_lwip.c` for a reference socket driver implementation using LWIP.
|
||||
|
||||
.. note::
|
||||
If you use :cpp:func:`select` for socket file descriptors only then you can enable the
|
||||
:envvar:`CONFIG_LWIP_USE_ONLY_LWIP_SELECT` option to reduce the code size and improve performance.
|
||||
|
||||
.. note::
|
||||
Don't change the socket driver during an active :cpp:func:`select` call or you might experience some undefined
|
||||
behavior.
|
||||
|
||||
Paths
|
||||
-----
|
||||
|
||||
Each registered FS has a path prefix associated with it. This prefix may be considered a "mount point" of this partition.
|
||||
Each registered FS has a path prefix associated with it. This prefix can be considered as 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:
|
||||
|
||||
@ -111,45 +177,49 @@ 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``.
|
||||
- Even if ``/index.html"`` does not 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.
|
||||
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 might be used in cases when an application needs to provide a "fallback" filesystem or to 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.
|
||||
VFS does not handle dots (``.``) in path names in any special way. VFS does not treat ``..`` as a reference to the parent directory. 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) might handle dots in file names differently.
|
||||
|
||||
When opening files, FS driver will only be given relative path to files. For example:
|
||||
When opening files, the FS driver receives only relative paths 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
|
||||
1. The ``myfs`` driver is registered with ``/data`` as a path prefix.
|
||||
2. The application calls ``fopen("/data/config.json", ...)``.
|
||||
3. The VFS component calls ``myfs_open("/config.json", ...)``.
|
||||
4. The ``myfs`` driver opens the ``/config.json`` file.
|
||||
|
||||
VFS does not impose any limit on total file path length, but it does limit the FS path prefix to ``ESP_VFS_PATH_MAX`` characters. Individual FS drivers may have their own filename length limitations.
|
||||
|
||||
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.
|
||||
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.
|
||||
If the menuconfig option ``UART for console output`` 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; 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.
|
||||
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. Due to this non-blocking read behavior, higher level C library calls, such as ``fscanf("%d\n", &var);``, might 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``.
|
||||
Applications which use the UART driver can instruct VFS to use the driver's interrupt driven, blocking read and write functions instead. This can be done using a call to the ``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.
|
||||
VFS also provides an optional newline conversion feature for input and output. Internally, most applications send and receive lines terminated by the 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 the functions ``esp_vfs_dev_uart_set_rx_line_endings`` and ``esp_vfs_dev_uart_set_tx_line_endings``.
|
||||
|
||||
|
||||
|
||||
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:
|
||||
``FILE`` objects for ``stdin``, ``stdout``, and ``stderr`` are shared between all FreeRTOS tasks, but the pointers to these objects are stored in per-task ``struct _reent``.
|
||||
|
||||
The following code is transferred to ``fprintf(__getreent()->_stderr, "42\n");`` by the preprocessor:
|
||||
|
||||
.. highlight:: c
|
||||
|
||||
@ -157,14 +227,11 @@ Standard streams and FreeRTOS tasks
|
||||
|
||||
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).
|
||||
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.
|
||||
- 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, which 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.
|
||||
|
167
components/vfs/README_CN.rst
Normal file
167
components/vfs/README_CN.rst
Normal file
@ -0,0 +1,167 @@
|
||||
虚拟文件系统组件
|
||||
============================
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
概述
|
||||
--------
|
||||
|
||||
虚拟文件系统 (VFS) 组件可为一些驱动提供一个统一接口。有了该接口,用户可像操作普通文件一样操作虚拟文件。这类驱动程序可以是 FAT、SPIFFS 等真实文件系统,也可以是有文件类接口的设备驱动程序。
|
||||
|
||||
VFS 组件支持 C 库函数(如 fopen 和 fprintf 等)与文件系统 (FS) 驱动程序协同工作。在高层级,每个 FS 驱动程序均与某些路径前缀相关联。当一个 C 库函数需要打开文件时,VFS 组件将搜索与该文件所在文件路径相关联的 FS 驱动程序,并将调用传递给该驱动程序。针对该文件的读取、写入等其他操作的调用也将传递给这个驱动程序。
|
||||
|
||||
例如,您可以使用 ``/fat`` 前缀注册 FAT 文件系统驱动,之后即可调用 ``fopen("/fat/file.txt", "w")``。之后,VFS 将调用 FAT 驱动的 ``open`` 函数,并将参数 ``/file.txt`` 和合适的打开模式传递给 ``open`` 函数;后续对返回的 ``FILE*`` 数据流调用 C 库函数也同样会传递给 FAT 驱动。
|
||||
|
||||
注册 FS 驱动程序
|
||||
---------------------
|
||||
|
||||
如需注册 FS 驱动程序,首先要定义一个 :cpp:type:`esp_vfs_t` 结构体实例,并用指向 FS API 的函数指针填充它。
|
||||
|
||||
.. 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));
|
||||
|
||||
在上述代码中需要用到 ``read``、 ``write`` 或 ``read_p``、 ``write_p``,具体使用哪组函数由 FS 驱动程序 API 的声明方式决定。
|
||||
|
||||
示例 1:声明 API 函数时不带额外的上下文指针参数,即 FS 驱动程序为单例模式,此时使用 ``write`` ::
|
||||
|
||||
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));
|
||||
|
||||
示例 2:声明 API 函数时需要一个额外的上下文指针作为参数,即可支持多个 FS 驱动程序实例,此时使用 ``write_p`` ::
|
||||
|
||||
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));
|
||||
|
||||
同步输入/输出多路复用
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
如需通过 :cpp:func:`select` 使用同步输入/输出多路复用,首先需要把 :cpp:func:`start_select` 和 :cpp:func:`end_select` 注册到 VFS,如下所示:
|
||||
|
||||
.. 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` 设置环境,用以检测某一 VFS 文件描述符的读取/写入/错误条件。调用 :cpp:func:`end_select` 终止、析构或释放 :cpp:func:`start_select` 设置的资源。请在 :component_file:`vfs/vfs_uart.c` 中查看 UART 外设参考实现、:cpp:func:`esp_vfs_dev_uart_register`、:cpp:func:`uart_start_select` 和 :cpp:func:`uart_end_select` 函数。
|
||||
|
||||
请参考以下示例,查看如何使用 VFS 文件描述符调用 :cpp:func:`select`:
|
||||
|
||||
- :example:`peripherals/uart_select`
|
||||
- :example:`system/select`
|
||||
|
||||
如果 :cpp:func:`select` 用于套接字文件描述符,您可以启用 :envvar:`CONFIG_LWIP_USE_ONLY_LWIP_SELECT` 选项来减少代码量,提高性能。
|
||||
|
||||
路径
|
||||
-----
|
||||
|
||||
已注册的 FS 驱动程序均有一个路径前缀与之关联,此路径前缀即为分区的挂载点。
|
||||
|
||||
如果挂载点中嵌套了其他挂载点,则在打开文件时使用具有最长匹配路径前缀的挂载点。例如,假设以下文件系统已在 VFS 中注册:
|
||||
|
||||
- 在 /data 下注册 FS 驱动程序 1
|
||||
- 在 /data/static 下注册 FS 驱动程序 2
|
||||
|
||||
那么:
|
||||
|
||||
- 打开 ``/data/log.txt`` 会调用驱动程序 FS 1;
|
||||
- 打开 ``/data/static/index.html`` 需调用 FS 驱动程序 2;
|
||||
- 即便 FS 驱动程序 2 中没有 ``/index.html``,也不会在 FS 驱动程序 1 中查找 ``/static/index.html``。
|
||||
|
||||
挂载点名称必须以路径分隔符 (``/``) 开头,且分隔符后至少包含一个字符。但在以下情况中,VFS 同样支持空的挂载点名称:1. 应用程序需要提供一个”最后方案“下使用的文件系统;2. 应用程序需要同时覆盖 VFS 功能。如果没有与路径匹配的前缀,就会使用到这种文件系统。
|
||||
|
||||
VFS 不会对路径中的点 (``.``) 进行特殊处理,也不会将 ``..`` 视为对父目录的引用。在上述示例中,使用 ``/data/static/../log.txt`` 路径不会调用 FS 驱动程序 1 打开 ``/log.txt``。特定的 FS 驱动程序(如 FATFS)可能以不同的方式处理文件名中的点。
|
||||
|
||||
执行打开文件操作时,FS 驱动程序仅得到文件的相对路径(挂载点前缀已经被去除):
|
||||
|
||||
1. 以 ``/data`` 为路径前缀注册 ``myfs`` 驱动;
|
||||
2. 应用程序调用 ``fopen("/data/config.json", ...)``;
|
||||
3. VFS 调用 ``myfs_open("/config.json", ...)``;
|
||||
4. ``myfs`` 驱动打开 ``/config.json`` 文件。
|
||||
|
||||
VFS 对文件路径长度没有限制,但文件系统路径前缀受 ``ESP_VFS_PATH_MAX`` 限制,即路径前缀上限为 ``ESP_VFS_PATH_MAX``。各个文件系统驱动则可能会对自己的文件名长度设置一些限制。
|
||||
|
||||
|
||||
文件描述符
|
||||
----------------
|
||||
|
||||
文件描述符是一组很小的正整数,从 ``0`` 到 ``FD_SETSIZE - 1``,``FD_SETSIZE`` 在 newlib ``sys/types.h`` 中定义。最大文件描述符由 ``CONFIG_LWIP_MAX_SOCKETS`` 定义,且为套接字保留。VFS 中包含一个名为 ``s_fd_table`` 的查找表,用于将全局文件描述符映射至 ``s_vfs`` 数组中注册的 VFS 驱动索引。
|
||||
|
||||
|
||||
标准 IO 流 (stdin, stdout, stderr)
|
||||
-------------------------------------------
|
||||
|
||||
如果 menuconfig 中 ``UART for console output`` 选项没有设置为 ``None``,则 ``stdin``、 ``stdout`` 和 ``stderr`` 将默认从 UART 读取或写入。UART0 或 UART1 可用作标准 IO。默认情况下,UART0 使用 115200 波特率,TX 管脚为 GPIO1,RX 管脚为 GPIO3。您可以在 menuconfig 中更改上述参数。
|
||||
|
||||
对 ``stdout`` 或 ``stderr`` 执行写入操作将会向 UART 发送 FIFO 发送字符,对 ``stdin`` 执行读取操作则会从 UART 接收 FIFO 中取出字符。
|
||||
|
||||
默认情况下,VFS 使用简单的函数对 UART 进行读写操作。在所有数据放进 UART FIFO 之前,写操作将处于 busy-wait 状态,读操处于非阻塞状态,仅返回 FIFO 中已有数据。由于读操作为非阻塞,高层级 C 库函数调用(如 ``fscanf("%d\n", &var);``)可能获取不到所需结果。
|
||||
|
||||
如果应用程序使用 UART 驱动,则可以调用 ``esp_vfs_dev_uart_use_driver`` 函数来指导 VFS 使用驱动中断、读写阻塞功能等。您也可以调用 ``esp_vfs_dev_uart_use_nonblocking`` 来恢复非阻塞函数。
|
||||
|
||||
VFS 还为输入和输出提供换行符转换功能(可选)。多数应用程序在程序内部发送或接收以 LF (''\n'') 结尾的行,但不同的终端程序可能需要不同的换行符,比如 CR 或 CRLF。应用程序可以通过 menuconfig 或者调用 ``esp_vfs_dev_uart_set_rx_line_endings`` 和 ``esp_vfs_dev_uart_set_tx_line_endings`` 为输入输出配置换行符。
|
||||
|
||||
|
||||
标准流和 FreeRTOS 任务
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``stdin``、``stdout`` 和 ``stderr`` 的 ``FILE`` 对象在所有 FreeRTOS 任务之间共享,指向这些对象的指针分别存储在每个任务的 ``struct _reent`` 中。
|
||||
|
||||
预处理器把如下代码:
|
||||
|
||||
.. highlight:: c
|
||||
|
||||
::
|
||||
|
||||
fprintf(stderr, "42\n");
|
||||
|
||||
解释为:
|
||||
|
||||
.. highlight:: c
|
||||
|
||||
::
|
||||
|
||||
fprintf(__getreent()->_stderr, "42\n");
|
||||
|
||||
其中 ``__getreent()`` 函数将为每个任务返回一个指向 ``struct _reent`` 的指针 (:component_file:`newlib/include/sys/reent.h#L370-L417`)。每个任务的 TCB 均拥有一个 ``struct _reent`` 结构体,任务初始化后,``struct _reent`` 结构体中的 ``_stdin``、``_stdout`` 和 ``_stderr`` 将会被赋予 ``_GLOBAL_REENT`` 中 ``_stdin``、 ``_stdout`` 和 ``_stderr`` 的值,``_GLOBAL_REENT`` 即为 FreeRTOS 启动之前所用结构体。
|
||||
|
||||
这样设计带来的结果是:
|
||||
|
||||
- 允许重定向给定任务的 ``stdin``、 ``stdout`` 和 ``stderr``,而不影响其他任务,例如通过 ``stdin = fopen("/dev/uart/1", "r")``;
|
||||
- 但使用 ``fclose`` 关闭默认 ``stdin``、 ``stdout`` 或 ``stderr`` 将同时关闭相应的 ``FILE`` 流对象,因此会影响其他任务;
|
||||
- 如需更改新任务的默认 ``stdin``、 ``stdout`` 和 ``stderr`` 流,请在创建新任务之前修改 ``_GLOBAL_REENT->_stdin`` (``_stdout``、``_stderr``)。
|
@ -3,7 +3,3 @@
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
ifndef CONFIG_USING_ESP_VFS
|
||||
COMPONENT_SRCDIRS := port
|
||||
endif
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
// Copyright 2015-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.
|
||||
@ -66,6 +66,16 @@ extern "C" {
|
||||
*/
|
||||
typedef int esp_vfs_id_t;
|
||||
|
||||
/**
|
||||
* @brief VFS semaphore type for select()
|
||||
*
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
bool is_sem_local; /*!< type of "sem" is SemaphoreHandle_t when true, defined by socket driver otherwise */
|
||||
void *sem; /*!< semaphore instance */
|
||||
} esp_vfs_select_sem_t;
|
||||
|
||||
/**
|
||||
* @brief VFS definition structure
|
||||
*
|
||||
@ -102,6 +112,14 @@ typedef struct
|
||||
ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size);
|
||||
ssize_t (*read)(int fd, void * dst, size_t size);
|
||||
};
|
||||
union {
|
||||
ssize_t (*pread_p)(void *ctx, int fd, void * dst, size_t size, off_t offset);
|
||||
ssize_t (*pread)(int fd, void * dst, size_t size, off_t offset);
|
||||
};
|
||||
union {
|
||||
ssize_t (*pwrite_p)(void *ctx, int fd, const void *src, size_t size, off_t offset);
|
||||
ssize_t (*pwrite)(int fd, const void *src, size_t size, off_t offset);
|
||||
};
|
||||
union {
|
||||
int (*open_p)(void* ctx, const char * path, int flags, int mode);
|
||||
int (*open)(const char * path, int flags, int mode);
|
||||
@ -163,8 +181,8 @@ typedef struct
|
||||
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);
|
||||
int (*fcntl_p)(void* ctx, int fd, int cmd, int arg);
|
||||
int (*fcntl)(int fd, int cmd, int arg);
|
||||
};
|
||||
union {
|
||||
int (*ioctl_p)(void* ctx, int fd, int cmd, va_list args);
|
||||
@ -182,7 +200,11 @@ typedef struct
|
||||
int (*truncate_p)(void* ctx, const char *path, off_t length);
|
||||
int (*truncate)(const char *path, off_t length);
|
||||
};
|
||||
#ifdef CONFIG_SUPPORT_TERMIOS
|
||||
union {
|
||||
int (*utime_p)(void* ctx, const char *path, const struct utimbuf *times);
|
||||
int (*utime)(const char *path, const struct utimbuf *times);
|
||||
};
|
||||
#ifdef CONFIG_VFS_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);
|
||||
@ -211,18 +233,20 @@ typedef struct
|
||||
int (*tcsendbreak_p)(void *ctx, int fd, int duration);
|
||||
int (*tcsendbreak)(int fd, int duration);
|
||||
};
|
||||
#endif // CONFIG_SUPPORT_TERMIOS
|
||||
#endif // CONFIG_VFS_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);
|
||||
esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, esp_vfs_select_sem_t sem, void **end_select_args);
|
||||
/** 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)();
|
||||
void (*stop_socket_select)(void *sem);
|
||||
/** stop_socket_select which can be called from ISR; set only for the socket driver */
|
||||
void (*stop_socket_select_isr)(BaseType_t *woken);
|
||||
void (*stop_socket_select_isr)(void *sem, 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)();
|
||||
void* (*get_socket_select_semaphore)(void);
|
||||
/** get_socket_select_semaphore returns semaphore allocated in the socket driver; set only for the socket driver */
|
||||
esp_err_t (*end_select)(void *end_select_args);
|
||||
} esp_vfs_t;
|
||||
|
||||
|
||||
@ -332,6 +356,7 @@ int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st);
|
||||
int esp_vfs_link(struct _reent *r, const char* n1, const char* n2);
|
||||
int esp_vfs_unlink(struct _reent *r, const char *path);
|
||||
int esp_vfs_rename(struct _reent *r, const char *src, const char *dst);
|
||||
int esp_vfs_utime(const char *path, const struct utimbuf *times);
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
@ -366,9 +391,9 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds
|
||||
* This function is called when the VFS driver detects a read/write/error
|
||||
* condition as it was requested by the previous call to start_select.
|
||||
*
|
||||
* @param signal_sem semaphore handle which was passed to the driver by the start_select call
|
||||
* @param sem semaphore structure which was passed to the driver by the start_select call
|
||||
*/
|
||||
void esp_vfs_select_triggered(SemaphoreHandle_t *signal_sem);
|
||||
void esp_vfs_select_triggered(esp_vfs_select_sem_t sem);
|
||||
|
||||
/**
|
||||
* @brief Notification from a VFS driver about a read/write/error condition (ISR version)
|
||||
@ -376,10 +401,10 @@ void esp_vfs_select_triggered(SemaphoreHandle_t *signal_sem);
|
||||
* This function is called when the VFS driver detects a read/write/error
|
||||
* condition as it was requested by the previous call to start_select.
|
||||
*
|
||||
* @param signal_sem semaphore handle which was passed to the driver by the start_select call
|
||||
* @param sem semaphore structure which was passed to the driver by the start_select call
|
||||
* @param woken is set to pdTRUE if the function wakes up a task with higher priority
|
||||
*/
|
||||
void esp_vfs_select_triggered_isr(SemaphoreHandle_t *signal_sem, BaseType_t *woken);
|
||||
void esp_vfs_select_triggered_isr(esp_vfs_select_sem_t sem, BaseType_t *woken);
|
||||
|
||||
/**
|
||||
* @brief Implements the VFS layer for synchronous I/O multiplexing by poll()
|
||||
@ -395,7 +420,37 @@ void esp_vfs_select_triggered_isr(SemaphoreHandle_t *signal_sem, BaseType_t *wok
|
||||
* return value indicates a timed-out poll. -1 is return on failure and errno is set accordingly.
|
||||
*
|
||||
*/
|
||||
int esp_vfs_poll(struct pollfd *fds, int nfds, int timeout);
|
||||
int esp_vfs_poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Implements the VFS layer of POSIX pread()
|
||||
*
|
||||
* @param fd File descriptor used for read
|
||||
* @param dst Pointer to the buffer where the output will be written
|
||||
* @param size Number of bytes to be read
|
||||
* @param offset Starting offset of the read
|
||||
*
|
||||
* @return A positive return value indicates the number of bytes read. -1 is return on failure and errno is
|
||||
* set accordingly.
|
||||
*/
|
||||
ssize_t esp_vfs_pread(int fd, void *dst, size_t size, off_t offset);
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Implements the VFS layer of POSIX pwrite()
|
||||
*
|
||||
* @param fd File descriptor used for write
|
||||
* @param src Pointer to the buffer from where the output will be read
|
||||
* @param size Number of bytes to write
|
||||
* @param offset Starting offset of the write
|
||||
*
|
||||
* @return A positive return value indicates the number of bytes written. -1 is return on failure and errno is
|
||||
* set accordingly.
|
||||
*/
|
||||
ssize_t esp_vfs_pwrite(int fd, const void *src, size_t size, off_t offset);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
@ -34,7 +34,7 @@ typedef enum {
|
||||
*
|
||||
* This function is called from startup code to enable serial output
|
||||
*/
|
||||
void esp_vfs_dev_uart_register();
|
||||
void esp_vfs_dev_uart_register(void);
|
||||
|
||||
/**
|
||||
* @brief Set the line endings expected to be received on UART
|
||||
|
46
components/vfs/include/esp_vfs_semihost.h
Normal file
46
components/vfs/include/esp_vfs_semihost.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_vfs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief add virtual filesystem semihosting driver
|
||||
*
|
||||
* @param base_path VFS path to mount host directory
|
||||
* @param host_path host path to mount; if NULL default dirctory will be used (see OpenOCD configuration)
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if esp_vfs_semihost_register was already called for specified VFS path
|
||||
* - ESP_ERR_NO_MEM if there are no slots to register new mount point
|
||||
*/
|
||||
esp_err_t esp_vfs_semihost_register(const char* base_path, const char* host_path);
|
||||
|
||||
/**
|
||||
* @brief Un-register semihosting driver from VFS
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if semihosting driver is not registered in VFS at that path
|
||||
*/
|
||||
esp_err_t esp_vfs_semihost_unregister(const char* base_path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -1,39 +0,0 @@
|
||||
// Copyright 2019-2020 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"
|
||||
|
||||
#ifndef CONFIG_USING_ESP_VFS
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
int fcntl(int fd, int request, ...)
|
||||
{
|
||||
int val, ret;
|
||||
va_list va;
|
||||
|
||||
va_start(va, request);
|
||||
|
||||
val = va_arg(va, int);
|
||||
ret = lwip_fcntl(fd, request, val);
|
||||
|
||||
va_end(va);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* !CONFIG_USING_ESP_VFS */
|
@ -1,42 +0,0 @@
|
||||
// Copyright 2019-2020 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"
|
||||
|
||||
#ifndef CONFIG_USING_ESP_VFS
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <lwip/sockets.h>
|
||||
|
||||
#undef ioctl
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endif /* !CONFIG_USING_ESP_VFS */
|
5
components/vfs/sdkconfig.rename
Normal file
5
components/vfs/sdkconfig.rename
Normal file
@ -0,0 +1,5 @@
|
||||
# sdkconfig replacement configurations for deprecated options formatted as
|
||||
# CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION
|
||||
|
||||
CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT
|
||||
CONFIG_SUPPORT_TERMIOS CONFIG_VFS_SUPPORT_TERMIOS
|
3
components/vfs/test/CMakeLists.txt
Normal file
3
components/vfs/test/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRC_DIRS "."
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES unity test_utils vfs fatfs spiffs)
|
1
components/vfs/test/component.mk
Normal file
1
components/vfs/test/component.mk
Normal file
@ -0,0 +1 @@
|
||||
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
|
137
components/vfs/test/test_vfs_access.c
Normal file
137
components/vfs/test/test_vfs_access.c
Normal file
@ -0,0 +1,137 @@
|
||||
// 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.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include "unity.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "wear_levelling.h"
|
||||
|
||||
static wl_handle_t test_wl_handle;
|
||||
|
||||
TEST_CASE("Can use access() for UART", "[vfs]")
|
||||
{
|
||||
const char *uarts[] = {
|
||||
"/dev/uart/0",
|
||||
"/dev/uart/1",
|
||||
#if SOC_UART_NUM > 2
|
||||
"/dev/uart/2"
|
||||
#endif
|
||||
};
|
||||
|
||||
uart_driver_install(UART_NUM_0, 256, 0, 0, NULL, 0);
|
||||
uart_driver_install(UART_NUM_1, 256, 0, 0, NULL, 0);
|
||||
#if SOC_UART_NUM > 2
|
||||
uart_driver_install(UART_NUM_2, 256, 0, 0, NULL, 0);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < sizeof(uarts)/sizeof(uarts[0]); ++i) {
|
||||
TEST_ASSERT_EQUAL_MESSAGE(access(uarts[i], F_OK), 0, uarts[i]);
|
||||
|
||||
TEST_ASSERT_EQUAL_MESSAGE(access(uarts[i], R_OK), 0, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(access(uarts[i], W_OK), 0, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(access(uarts[i], X_OK), -1, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(errno, EACCES, uarts[i]);
|
||||
|
||||
TEST_ASSERT_EQUAL_MESSAGE(access(uarts[i], R_OK | W_OK), 0, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(access(uarts[i], R_OK | X_OK), -1, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(errno, EACCES, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(access(uarts[i], W_OK | X_OK), -1, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(errno, EACCES, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(access(uarts[i], R_OK | W_OK | X_OK), -1, uarts[i]);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(errno, EACCES, uarts[i]);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL(access("/dev/uart/3", F_OK), -1);
|
||||
TEST_ASSERT_EQUAL(errno, ENOENT);
|
||||
|
||||
uart_driver_delete(UART_NUM_0);
|
||||
uart_driver_delete(UART_NUM_1);
|
||||
#if SOC_UART_NUM > 2
|
||||
uart_driver_delete(UART_NUM_2);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void test_spi_flash_setup(void)
|
||||
{
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5
|
||||
};
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_mount("/spiflash", NULL, &mount_config, &test_wl_handle));
|
||||
}
|
||||
|
||||
static inline void test_spi_flash_teardown(void)
|
||||
{
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount("/spiflash", test_wl_handle));
|
||||
}
|
||||
|
||||
static inline void test_fatfs_create_file(const char *name)
|
||||
{
|
||||
int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
TEST_ASSERT_NOT_EQUAL(fd, -1);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
}
|
||||
|
||||
static inline void test_fatfs_delete_file(const char *name)
|
||||
{
|
||||
int ret = unlink(name);
|
||||
TEST_ASSERT_EQUAL(ret, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Can use access() for FATFS", "[vfs][fatfs][wear_levelling]")
|
||||
{
|
||||
const char *path = "/spiflash/access.txt";
|
||||
|
||||
test_spi_flash_setup();
|
||||
|
||||
{
|
||||
int ret = access(path, F_OK);
|
||||
|
||||
if (ret != -1) {
|
||||
// it wasn't deleted before so we delete it now to pass the test
|
||||
// case the next time
|
||||
test_fatfs_delete_file(path);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL(ret, -1);
|
||||
TEST_ASSERT_EQUAL(errno, ENOENT);
|
||||
}
|
||||
|
||||
test_fatfs_create_file(path);
|
||||
TEST_ASSERT_EQUAL(access(path, F_OK), 0);
|
||||
|
||||
TEST_ASSERT_EQUAL(access(path, R_OK), 0);
|
||||
TEST_ASSERT_EQUAL(access(path, W_OK), 0);
|
||||
TEST_ASSERT_EQUAL(access(path, X_OK), 0);
|
||||
TEST_ASSERT_EQUAL(access(path, R_OK | W_OK), 0);
|
||||
TEST_ASSERT_EQUAL(access(path, R_OK | X_OK), 0);
|
||||
TEST_ASSERT_EQUAL(access(path, W_OK | X_OK), 0);
|
||||
TEST_ASSERT_EQUAL(access(path, R_OK | W_OK | X_OK), 0);
|
||||
|
||||
//TODO f_chmod the file and re-test the access rights (this requires
|
||||
// f_chmod support to be implemented in VFS)
|
||||
|
||||
test_fatfs_delete_file(path);
|
||||
TEST_ASSERT_EQUAL(access(path, F_OK), -1);
|
||||
TEST_ASSERT_EQUAL(errno, ENOENT);
|
||||
|
||||
test_spi_flash_teardown();
|
||||
}
|
116
components/vfs/test/test_vfs_append.c
Normal file
116
components/vfs/test/test_vfs_append.c
Normal file
@ -0,0 +1,116 @@
|
||||
// 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.
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include "unity.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "wear_levelling.h"
|
||||
|
||||
#define TEST_PARTITION_LABEL "flash_test"
|
||||
|
||||
#define OPEN_MODE 0
|
||||
#define MSG1 "Hello"
|
||||
#define MSG2 " "
|
||||
#define MSG3 "world!"
|
||||
|
||||
static inline void test_write(int fd, const char *str, const char *msg)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_MESSAGE(strlen(str), write(fd, str, strlen(str)), msg);
|
||||
}
|
||||
|
||||
static inline void test_read(int fd, const char *str, const char *msg)
|
||||
{
|
||||
char buf[strlen(str)];
|
||||
TEST_ASSERT_EQUAL_MESSAGE(strlen(str), read(fd, buf, strlen(str)), msg);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(str, buf, strlen(str), msg);
|
||||
}
|
||||
|
||||
static inline void test_read_fails(int fd, const char *msg)
|
||||
{
|
||||
char buf;
|
||||
TEST_ASSERT_EQUAL_MESSAGE(0, read(fd, &buf, 1), msg);
|
||||
}
|
||||
|
||||
static void test_append(const char *path)
|
||||
{
|
||||
int fd = open(path, O_RDWR | O_APPEND | O_CREAT | O_TRUNC, OPEN_MODE);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
test_write(fd, MSG1, "write MSG1");
|
||||
test_read_fails(fd, "read fails MSG1");
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
test_read(fd, MSG1, "read MSG1");
|
||||
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
test_write(fd, MSG2, "write MSG2");
|
||||
test_read_fails(fd, "read fails MSG2"); //because write moved the pointer
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
test_read(fd, MSG1 MSG2, "read MSG1 + MSG2");
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, close(fd));
|
||||
fd = open(path, O_RDWR | O_APPEND, OPEN_MODE);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
//after reopening the pointer should be at the beginning
|
||||
test_read(fd, MSG1 MSG2, "read reopening");
|
||||
|
||||
lseek(fd, strlen(MSG1), SEEK_SET);
|
||||
test_read(fd, MSG2, "read MSG2");
|
||||
lseek(fd, strlen(MSG1), SEEK_SET);
|
||||
test_write(fd, MSG3, "write MSG3");
|
||||
test_read_fails(fd, "read fails MSG3"); //because write moved the pointer
|
||||
lseek(fd, strlen(MSG1), SEEK_SET);
|
||||
test_read(fd, MSG2 MSG3, "read MSG2 + MSG3");
|
||||
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
test_read(fd, MSG1 MSG2 MSG3, "read MSG1 + MSG2 + MSG3");
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, close(fd));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, unlink(path));
|
||||
}
|
||||
|
||||
TEST_CASE("open() with O_APPEND on FATFS works well", "[vfs][FATFS]")
|
||||
{
|
||||
wl_handle_t test_wl_handle;
|
||||
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 2
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_mount("/spiflash", NULL, &mount_config, &test_wl_handle));
|
||||
|
||||
test_append("/spiflash/file.txt");
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount("/spiflash", test_wl_handle));
|
||||
}
|
||||
|
||||
TEST_CASE("open() with O_APPEND on SPIFFS works well", "[vfs][spiffs]")
|
||||
{
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/spiffs",
|
||||
.partition_label = TEST_PARTITION_LABEL,
|
||||
.max_files = 2,
|
||||
.format_if_mount_failed = true
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_spiffs_register(&conf));
|
||||
|
||||
test_append("/spiffs/file.txt");
|
||||
|
||||
TEST_ESP_OK(esp_vfs_spiffs_unregister(TEST_PARTITION_LABEL));
|
||||
}
|
268
components/vfs/test/test_vfs_fd.c
Normal file
268
components/vfs/test/test_vfs_fd.c
Normal file
@ -0,0 +1,268 @@
|
||||
// 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "unity.h"
|
||||
#include "esp_log.h"
|
||||
#include "test_utils.h"
|
||||
#include "ccomp_timer.h"
|
||||
|
||||
#define VFS_PREF1 "/vfs1"
|
||||
#define VFS_PREF2 "/vfs2"
|
||||
#define FILE1 "/file1"
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
int fd;
|
||||
} collision_test_vfs_param_t;
|
||||
|
||||
static int collision_test_vfs_open(void* ctx, const char * path, int flags, int mode)
|
||||
{
|
||||
const collision_test_vfs_param_t *param = (collision_test_vfs_param_t *) ctx;
|
||||
if (strcmp(param->path, path) == 0) {
|
||||
return param->fd;
|
||||
}
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int collision_test_vfs_close(void* ctx, int fd)
|
||||
{
|
||||
const collision_test_vfs_param_t *param = (collision_test_vfs_param_t *) ctx;
|
||||
if (fd == param->fd) {
|
||||
return 0;
|
||||
}
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
TEST_CASE("FDs from different VFSs don't collide", "[vfs]")
|
||||
{
|
||||
collision_test_vfs_param_t param = {
|
||||
.path = FILE1,
|
||||
.fd = 1,
|
||||
};
|
||||
|
||||
esp_vfs_t desc = {
|
||||
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
|
||||
.open_p = collision_test_vfs_open,
|
||||
.close_p = collision_test_vfs_close,
|
||||
};
|
||||
TEST_ESP_OK( esp_vfs_register(VFS_PREF1, &desc, ¶m) );
|
||||
TEST_ESP_OK( esp_vfs_register(VFS_PREF2, &desc, ¶m) );
|
||||
|
||||
const int fd1 = open(VFS_PREF1 FILE1, 0, 0);
|
||||
const int fd2 = open(VFS_PREF2 FILE1, 0, 0);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(fd1, -1);
|
||||
TEST_ASSERT_NOT_EQUAL(fd2, -1);
|
||||
// Both VFS drivers return local FD 1 but the global FDs returned by
|
||||
// open() should not be the same
|
||||
TEST_ASSERT_NOT_EQUAL(fd1, fd2);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(close(fd1), -1);
|
||||
TEST_ASSERT_NOT_EQUAL(close(fd2), -1);
|
||||
|
||||
TEST_ESP_OK( esp_vfs_unregister(VFS_PREF1) );
|
||||
TEST_ESP_OK( esp_vfs_unregister(VFS_PREF2) );
|
||||
}
|
||||
|
||||
#define FILE2 "/file2"
|
||||
#define FILE3 "/file3"
|
||||
#define FILE4 "/file4"
|
||||
#define CONCURRENT_TEST_STACK_SIZE (2*1024)
|
||||
#define CONCURRENT_TEST_MAX_WAIT (1000 / portTICK_PERIOD_MS)
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
SemaphoreHandle_t done;
|
||||
} concurrent_test_task_param_t;
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
int local_fd;
|
||||
} concurrent_test_path_to_fd_t;
|
||||
|
||||
static concurrent_test_path_to_fd_t concurrent_test_path_to_fd[] = {
|
||||
{ .path = FILE1, .local_fd = 1 },
|
||||
{ .path = FILE2, .local_fd = 2 },
|
||||
{ .path = FILE3, .local_fd = 3 },
|
||||
{ .path = FILE4, .local_fd = 4 },
|
||||
};
|
||||
|
||||
static int concurrent_test_vfs_open(const char * path, int flags, int mode)
|
||||
{
|
||||
for (int i = 0; i < sizeof(concurrent_test_path_to_fd)/sizeof(concurrent_test_path_to_fd[0]); ++i) {
|
||||
if (strcmp(concurrent_test_path_to_fd[i].path, path) == 0) {
|
||||
// This behaves like UART: opening the same file gives always the
|
||||
// same local FD (even when opening at the same time multiple FDs)
|
||||
return concurrent_test_path_to_fd[i].local_fd;
|
||||
}
|
||||
}
|
||||
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int concurrent_test_vfs_close(int fd)
|
||||
{
|
||||
for (int i = 0; i < sizeof(concurrent_test_path_to_fd)/sizeof(concurrent_test_path_to_fd[0]); ++i) {
|
||||
if (concurrent_test_path_to_fd[i].local_fd == fd) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline void test_delay_rand_ms(int ms)
|
||||
{
|
||||
vTaskDelay((rand() % ms) / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
static void concurrent_task(void *param)
|
||||
{
|
||||
concurrent_test_task_param_t *task_param = (concurrent_test_task_param_t *) param;
|
||||
|
||||
test_delay_rand_ms(10);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
const int global_fd = open(task_param->path, 0, 0);
|
||||
TEST_ASSERT_NOT_EQUAL(global_fd, -1);
|
||||
test_delay_rand_ms(10);
|
||||
TEST_ASSERT_NOT_EQUAL(close(global_fd), -1);
|
||||
test_delay_rand_ms(10);
|
||||
}
|
||||
xSemaphoreGive(task_param->done);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("VFS can handle concurrent open/close requests", "[vfs]")
|
||||
{
|
||||
esp_vfs_t desc = {
|
||||
.flags = ESP_VFS_FLAG_DEFAULT,
|
||||
.open = concurrent_test_vfs_open,
|
||||
.close = concurrent_test_vfs_close,
|
||||
};
|
||||
|
||||
TEST_ESP_OK( esp_vfs_register(VFS_PREF1, &desc, NULL) );
|
||||
|
||||
concurrent_test_task_param_t param1 = { .path = VFS_PREF1 FILE1, .done = xSemaphoreCreateBinary() };
|
||||
concurrent_test_task_param_t param2 = { .path = VFS_PREF1 FILE1, .done = xSemaphoreCreateBinary() };
|
||||
concurrent_test_task_param_t param3 = { .path = VFS_PREF1 FILE2, .done = xSemaphoreCreateBinary() };
|
||||
concurrent_test_task_param_t param4 = { .path = VFS_PREF1 FILE2, .done = xSemaphoreCreateBinary() };
|
||||
concurrent_test_task_param_t param5 = { .path = VFS_PREF1 FILE3, .done = xSemaphoreCreateBinary() };
|
||||
concurrent_test_task_param_t param6 = { .path = VFS_PREF1 FILE3, .done = xSemaphoreCreateBinary() };
|
||||
concurrent_test_task_param_t param7 = { .path = VFS_PREF1 FILE4, .done = xSemaphoreCreateBinary() };
|
||||
concurrent_test_task_param_t param8 = { .path = VFS_PREF1 FILE4, .done = xSemaphoreCreateBinary() };
|
||||
|
||||
TEST_ASSERT_NOT_NULL(param1.done);
|
||||
TEST_ASSERT_NOT_NULL(param2.done);
|
||||
TEST_ASSERT_NOT_NULL(param3.done);
|
||||
TEST_ASSERT_NOT_NULL(param4.done);
|
||||
TEST_ASSERT_NOT_NULL(param5.done);
|
||||
TEST_ASSERT_NOT_NULL(param6.done);
|
||||
TEST_ASSERT_NOT_NULL(param7.done);
|
||||
TEST_ASSERT_NOT_NULL(param8.done);
|
||||
|
||||
const int cpuid0 = 0;
|
||||
const int cpuid1 = portNUM_PROCESSORS - 1;
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
xTaskCreatePinnedToCore(concurrent_task, "t1", CONCURRENT_TEST_STACK_SIZE, ¶m1, 3, NULL, cpuid0);
|
||||
xTaskCreatePinnedToCore(concurrent_task, "t2", CONCURRENT_TEST_STACK_SIZE, ¶m2, 3, NULL, cpuid1);
|
||||
xTaskCreatePinnedToCore(concurrent_task, "t3", CONCURRENT_TEST_STACK_SIZE, ¶m3, 3, NULL, cpuid0);
|
||||
xTaskCreatePinnedToCore(concurrent_task, "t4", CONCURRENT_TEST_STACK_SIZE, ¶m4, 3, NULL, cpuid1);
|
||||
xTaskCreatePinnedToCore(concurrent_task, "t5", CONCURRENT_TEST_STACK_SIZE, ¶m5, 3, NULL, cpuid0);
|
||||
xTaskCreatePinnedToCore(concurrent_task, "t6", CONCURRENT_TEST_STACK_SIZE, ¶m6, 3, NULL, cpuid1);
|
||||
xTaskCreatePinnedToCore(concurrent_task, "t7", CONCURRENT_TEST_STACK_SIZE, ¶m7, 3, NULL, cpuid0);
|
||||
xTaskCreatePinnedToCore(concurrent_task, "t8", CONCURRENT_TEST_STACK_SIZE, ¶m8, 3, NULL, cpuid1);
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(param1.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(param2.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(param3.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(param4.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(param5.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(param6.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(param7.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(param8.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
|
||||
|
||||
vSemaphoreDelete(param1.done);
|
||||
vSemaphoreDelete(param2.done);
|
||||
vSemaphoreDelete(param3.done);
|
||||
vSemaphoreDelete(param4.done);
|
||||
vSemaphoreDelete(param5.done);
|
||||
vSemaphoreDelete(param6.done);
|
||||
vSemaphoreDelete(param7.done);
|
||||
vSemaphoreDelete(param8.done);
|
||||
|
||||
TEST_ESP_OK( esp_vfs_unregister(VFS_PREF1) );
|
||||
}
|
||||
|
||||
static int time_test_vfs_open(const char *path, int flags, int mode)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int time_test_vfs_close(int fd)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int time_test_vfs_write(int fd, const void *data, size_t size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
TEST_CASE("Open & write & close through VFS passes performance test", "[vfs]")
|
||||
{
|
||||
esp_vfs_t desc = {
|
||||
.flags = ESP_VFS_FLAG_DEFAULT,
|
||||
.open = time_test_vfs_open,
|
||||
.close = time_test_vfs_close,
|
||||
.write = time_test_vfs_write,
|
||||
};
|
||||
|
||||
TEST_ESP_OK( esp_vfs_register(VFS_PREF1, &desc, NULL) );
|
||||
|
||||
ccomp_timer_start();
|
||||
const int iter_count = 5000;
|
||||
|
||||
for (int i = 0; i < iter_count; ++i) {
|
||||
const int fd = open(VFS_PREF1 FILE1, 0, 0);
|
||||
TEST_ASSERT_NOT_EQUAL(fd, -1);
|
||||
|
||||
write(fd, "a", 1);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(close(fd), -1);
|
||||
}
|
||||
|
||||
const int64_t time_diff_us = ccomp_timer_stop();
|
||||
const int ns_per_iter = (int) (time_diff_us * 1000 / iter_count);
|
||||
TEST_ESP_OK( esp_vfs_unregister(VFS_PREF1) );
|
||||
#ifdef CONFIG_SPIRAM
|
||||
TEST_PERFORMANCE_LESS_THAN(VFS_OPEN_WRITE_CLOSE_TIME_PSRAM, "%dns", ns_per_iter);
|
||||
#else
|
||||
TEST_PERFORMANCE_LESS_THAN(VFS_OPEN_WRITE_CLOSE_TIME, "%dns", ns_per_iter);
|
||||
#endif
|
||||
|
||||
}
|
283
components/vfs/test/test_vfs_paths.c
Normal file
283
components/vfs/test/test_vfs_paths.c
Normal file
@ -0,0 +1,283 @@
|
||||
// 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 <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/dirent.h>
|
||||
#include "esp_vfs.h"
|
||||
#include "unity.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
/* Dummy VFS implementation to check if VFS is called or not with expected path
|
||||
*/
|
||||
typedef struct {
|
||||
const char* match_path;
|
||||
bool called;
|
||||
} dummy_vfs_t;
|
||||
|
||||
static int dummy_open(void* ctx, const char * path, int flags, int mode)
|
||||
{
|
||||
dummy_vfs_t* dummy = (dummy_vfs_t*) ctx;
|
||||
dummy->called = true;
|
||||
if (strcmp(dummy->match_path, path) == 0) {
|
||||
return 1;
|
||||
}
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int dummy_close(void* ctx, int fd)
|
||||
{
|
||||
dummy_vfs_t* dummy = (dummy_vfs_t*) ctx;
|
||||
dummy->called = true;
|
||||
if (fd == 1) {
|
||||
return 0;
|
||||
}
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static DIR* dummy_opendir(void* ctx, const char* path)
|
||||
{
|
||||
dummy_vfs_t* dummy = (dummy_vfs_t*) ctx;
|
||||
dummy->called = true;
|
||||
if (strcmp(dummy->match_path, path) == 0) {
|
||||
DIR* result = calloc(1, sizeof(DIR));
|
||||
TEST_ASSERT_NOT_NULL(result);
|
||||
return result;
|
||||
}
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int dummy_closedir(void* ctx, DIR* pdir)
|
||||
{
|
||||
dummy_vfs_t* dummy = (dummy_vfs_t*) ctx;
|
||||
dummy->called = true;
|
||||
free(pdir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Initializer for this dummy VFS implementation
|
||||
*/
|
||||
|
||||
#define DUMMY_VFS() { \
|
||||
.flags = ESP_VFS_FLAG_CONTEXT_PTR, \
|
||||
.open_p = dummy_open, \
|
||||
.close_p = dummy_close, \
|
||||
.opendir_p = dummy_opendir, \
|
||||
.closedir_p = dummy_closedir \
|
||||
}
|
||||
|
||||
/* Helper functions to test VFS behavior
|
||||
*/
|
||||
|
||||
static void test_open(dummy_vfs_t* instance, const char* path,
|
||||
bool should_be_called, bool should_be_opened, int line)
|
||||
{
|
||||
const int flags = O_CREAT | O_TRUNC | O_RDWR;
|
||||
instance->called = false;
|
||||
int fd = esp_vfs_open(__getreent(), path, flags, 0);
|
||||
UNITY_TEST_ASSERT_EQUAL_INT(should_be_called, instance->called, line,
|
||||
"should_be_called check failed");
|
||||
if (should_be_called) {
|
||||
if (should_be_opened) {
|
||||
UNITY_TEST_ASSERT(fd >= 0, line, "should be opened");
|
||||
} else {
|
||||
UNITY_TEST_ASSERT(fd < 0, line, "should not be opened");
|
||||
}
|
||||
}
|
||||
esp_vfs_close(__getreent(), fd);
|
||||
}
|
||||
|
||||
static void test_opendir(dummy_vfs_t* instance, const char* path,
|
||||
bool should_be_called, bool should_be_opened, int line)
|
||||
{
|
||||
instance->called = false;
|
||||
DIR* dir = opendir(path);
|
||||
UNITY_TEST_ASSERT_EQUAL_INT(should_be_called, instance->called, line,
|
||||
"should_be_called check failed");
|
||||
if (should_be_called) {
|
||||
if (should_be_opened) {
|
||||
UNITY_TEST_ASSERT(dir != NULL, line, "should be opened");
|
||||
} else {
|
||||
UNITY_TEST_ASSERT(dir == 0, line, "should not be opened");
|
||||
}
|
||||
}
|
||||
if (dir) {
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper macros which forward line number to assertion macros inside test_open
|
||||
* and test_opendir
|
||||
*/
|
||||
|
||||
#define test_opened(instance, path) test_open(instance, path, true, true, __LINE__)
|
||||
#define test_not_opened(instance, path) test_open(instance, path, true, false, __LINE__)
|
||||
#define test_not_called(instance, path) test_open(instance, path, false, false, __LINE__)
|
||||
|
||||
#define test_dir_opened(instance, path) test_opendir(instance, path, true, true, __LINE__)
|
||||
#define test_dir_not_opened(instance, path) test_opendir(instance, path, true, false, __LINE__)
|
||||
#define test_dir_not_called(instance, path) test_opendir(instance, path, false, false, __LINE__)
|
||||
|
||||
|
||||
|
||||
TEST_CASE("vfs parses paths correctly", "[vfs]")
|
||||
{
|
||||
dummy_vfs_t inst_foo = {
|
||||
.match_path = "",
|
||||
.called = false
|
||||
};
|
||||
esp_vfs_t desc_foo = DUMMY_VFS();
|
||||
TEST_ESP_OK( esp_vfs_register("/foo", &desc_foo, &inst_foo) );
|
||||
|
||||
dummy_vfs_t inst_foo1 = {
|
||||
.match_path = "",
|
||||
.called = false
|
||||
};
|
||||
esp_vfs_t desc_foo1 = DUMMY_VFS();
|
||||
TEST_ESP_OK( esp_vfs_register("/foo1", &desc_foo1, &inst_foo1) );
|
||||
|
||||
inst_foo.match_path = "/file";
|
||||
test_opened(&inst_foo, "/foo/file");
|
||||
test_not_opened(&inst_foo, "/foo/file1");
|
||||
test_not_called(&inst_foo, "/foo1/file");
|
||||
test_not_called(&inst_foo, "/foo1");
|
||||
test_not_opened(&inst_foo, "/foo");
|
||||
inst_foo.match_path = "/junk";
|
||||
test_dir_opened(&inst_foo, "/foo/junk");
|
||||
inst_foo.match_path = "/";
|
||||
test_dir_opened(&inst_foo, "/foo/");
|
||||
test_dir_opened(&inst_foo, "/foo");
|
||||
test_dir_not_called(&inst_foo1, "/foo");
|
||||
test_dir_not_opened(&inst_foo, "/foo/1");
|
||||
test_dir_not_called(&inst_foo, "/foo1");
|
||||
|
||||
inst_foo1.match_path = "/file1";
|
||||
test_not_called(&inst_foo1, "/foo/file1");
|
||||
test_opened(&inst_foo1, "/foo1/file1");
|
||||
test_not_opened(&inst_foo1, "/foo1/file");
|
||||
|
||||
// Test nested VFS entries
|
||||
dummy_vfs_t inst_foobar = {
|
||||
.match_path = "",
|
||||
.called = false
|
||||
};
|
||||
esp_vfs_t desc_foobar = DUMMY_VFS();
|
||||
TEST_ESP_OK( esp_vfs_register("/foo/bar", &desc_foobar, &inst_foobar) );
|
||||
|
||||
dummy_vfs_t inst_toplevel = {
|
||||
.match_path = "",
|
||||
.called = false
|
||||
};
|
||||
esp_vfs_t desc_toplevel = DUMMY_VFS();
|
||||
TEST_ESP_OK( esp_vfs_register("", &desc_toplevel, &inst_toplevel) );
|
||||
|
||||
inst_foo.match_path = "/bar/file";
|
||||
inst_foobar.match_path = "/file";
|
||||
test_not_called(&inst_foo, "/foo/bar/file");
|
||||
test_opened(&inst_foobar, "/foo/bar/file");
|
||||
test_dir_not_called(&inst_foo, "/foo/bar/file");
|
||||
test_dir_opened(&inst_foobar, "/foo/bar/file");
|
||||
inst_toplevel.match_path = "/tmp/foo";
|
||||
test_opened(&inst_toplevel, "/tmp/foo");
|
||||
inst_toplevel.match_path = "foo";
|
||||
test_opened(&inst_toplevel, "foo");
|
||||
|
||||
TEST_ESP_OK( esp_vfs_unregister("/foo") );
|
||||
TEST_ESP_OK( esp_vfs_unregister("/foo1") );
|
||||
TEST_ESP_OK( esp_vfs_unregister("/foo/bar") );
|
||||
TEST_ESP_OK( esp_vfs_unregister("") );
|
||||
}
|
||||
|
||||
TEST_CASE("vfs unregisters correct nested mount point", "[vfs]")
|
||||
{
|
||||
dummy_vfs_t inst_foobar = {
|
||||
.match_path = "/file",
|
||||
.called = false
|
||||
};
|
||||
esp_vfs_t desc_foobar = DUMMY_VFS();
|
||||
TEST_ESP_OK( esp_vfs_register("/foo/bar", &desc_foobar, &inst_foobar) );
|
||||
|
||||
dummy_vfs_t inst_foo = {
|
||||
.match_path = "/bar/file",
|
||||
.called = false
|
||||
};
|
||||
esp_vfs_t desc_foo = DUMMY_VFS();
|
||||
TEST_ESP_OK( esp_vfs_register("/foo", &desc_foo, &inst_foo) );
|
||||
|
||||
/* basic operation */
|
||||
test_opened(&inst_foobar, "/foo/bar/file");
|
||||
test_not_called(&inst_foo, "/foo/bar/file");
|
||||
|
||||
/* this should not match anything */
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_vfs_unregister("/foo/b"));
|
||||
|
||||
/* unregister "/foo" and check that we haven't unregistered "/foo/bar" */
|
||||
TEST_ESP_OK( esp_vfs_unregister("/foo") );
|
||||
test_not_called(&inst_foo, "/foo/bar/file");
|
||||
test_opened(&inst_foobar, "/foo/bar/file");
|
||||
|
||||
/* repeat the above, with the reverse order of registration */
|
||||
TEST_ESP_OK( esp_vfs_unregister("/foo/bar") );
|
||||
TEST_ESP_OK( esp_vfs_register("/foo", &desc_foo, &inst_foo) );
|
||||
TEST_ESP_OK( esp_vfs_register("/foo/bar", &desc_foobar, &inst_foobar) );
|
||||
test_opened(&inst_foobar, "/foo/bar/file");
|
||||
test_not_called(&inst_foo, "/foo/bar/file");
|
||||
TEST_ESP_OK( esp_vfs_unregister("/foo") );
|
||||
test_not_called(&inst_foo, "/foo/bar/file");
|
||||
test_opened(&inst_foobar, "/foo/bar/file");
|
||||
TEST_ESP_OK( esp_vfs_unregister("/foo/bar") );
|
||||
}
|
||||
|
||||
|
||||
void test_vfs_register(const char* prefix, bool expect_success, int line)
|
||||
{
|
||||
dummy_vfs_t inst;
|
||||
esp_vfs_t desc = DUMMY_VFS();
|
||||
esp_err_t err = esp_vfs_register(prefix, &desc, &inst);
|
||||
if (expect_success) {
|
||||
UNITY_TEST_ASSERT_EQUAL_INT(ESP_OK, err, line, "esp_vfs_register should succeed");
|
||||
} else {
|
||||
UNITY_TEST_ASSERT_EQUAL_INT(ESP_ERR_INVALID_ARG,
|
||||
err, line, "esp_vfs_register should fail");
|
||||
}
|
||||
if (err == ESP_OK) {
|
||||
TEST_ESP_OK( esp_vfs_unregister(prefix) );
|
||||
}
|
||||
}
|
||||
|
||||
#define test_register_ok(prefix) test_vfs_register(prefix, true, __LINE__)
|
||||
#define test_register_fail(prefix) test_vfs_register(prefix, false, __LINE__)
|
||||
|
||||
TEST_CASE("vfs checks mount point path", "[vfs]")
|
||||
{
|
||||
test_register_ok("");
|
||||
test_register_fail("/");
|
||||
test_register_fail("a");
|
||||
test_register_fail("aa");
|
||||
test_register_fail("aaa");
|
||||
test_register_ok("/a");
|
||||
test_register_ok("/aa");
|
||||
test_register_ok("/aaa/bbb");
|
||||
test_register_fail("/aaa/");
|
||||
test_register_fail("/aaa/bbb/");
|
||||
test_register_ok("/23456789012345");
|
||||
test_register_fail("/234567890123456");
|
||||
}
|
614
components/vfs/test/test_vfs_select.c
Normal file
614
components/vfs/test/test_vfs_select.c
Normal file
@ -0,0 +1,614 @@
|
||||
// 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 <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/param.h>
|
||||
#include "unity.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/netdb.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
int delay_ms;
|
||||
xSemaphoreHandle sem;
|
||||
} test_task_param_t;
|
||||
|
||||
typedef struct {
|
||||
fd_set *rdfds;
|
||||
fd_set *wrfds;
|
||||
fd_set *errfds;
|
||||
int maxfds;
|
||||
struct timeval *tv;
|
||||
int select_ret;
|
||||
xSemaphoreHandle sem;
|
||||
} test_select_task_param_t;
|
||||
|
||||
static const char message[] = "Hello world!";
|
||||
|
||||
static int open_dummy_socket(void)
|
||||
{
|
||||
const struct addrinfo hints = {
|
||||
.ai_family = AF_INET,
|
||||
.ai_socktype = SOCK_DGRAM,
|
||||
};
|
||||
struct addrinfo *res = NULL;
|
||||
|
||||
const int err = getaddrinfo("localhost", "80", &hints, &res);
|
||||
TEST_ASSERT_EQUAL(0, err);
|
||||
TEST_ASSERT_NOT_NULL(res);
|
||||
|
||||
const int dummy_socket_fd = socket(res->ai_family, res->ai_socktype, 0);
|
||||
TEST_ASSERT(dummy_socket_fd >= 0);
|
||||
|
||||
return dummy_socket_fd;
|
||||
}
|
||||
|
||||
static int socket_init(void)
|
||||
{
|
||||
const struct addrinfo hints = {
|
||||
.ai_family = AF_INET,
|
||||
.ai_socktype = SOCK_DGRAM,
|
||||
};
|
||||
struct addrinfo *res;
|
||||
int err;
|
||||
struct sockaddr_in saddr = { 0 };
|
||||
int socket_fd = -1;
|
||||
|
||||
err = getaddrinfo("localhost", "80", &hints, &res);
|
||||
TEST_ASSERT_EQUAL(err, 0);
|
||||
TEST_ASSERT_NOT_NULL(res);
|
||||
|
||||
socket_fd = socket(res->ai_family, res->ai_socktype, 0);
|
||||
TEST_ASSERT(socket_fd >= 0);
|
||||
|
||||
saddr.sin_family = PF_INET;
|
||||
saddr.sin_port = htons(80);
|
||||
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
err = bind(socket_fd, (struct sockaddr *) &saddr, sizeof(struct sockaddr_in));
|
||||
TEST_ASSERT(err >= 0);
|
||||
|
||||
err = connect(socket_fd, res->ai_addr, res->ai_addrlen);
|
||||
TEST_ASSERT_EQUAL_MESSAGE(err, 0, "Socket connection failed");
|
||||
|
||||
freeaddrinfo(res);
|
||||
|
||||
return socket_fd;
|
||||
}
|
||||
|
||||
static void uart1_init(void)
|
||||
{
|
||||
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,
|
||||
.source_clk = UART_SCLK_APB,
|
||||
};
|
||||
uart_driver_install(UART_NUM_1, 256, 256, 0, NULL, 0);
|
||||
uart_param_config(UART_NUM_1, &uart_config);
|
||||
}
|
||||
|
||||
static void send_task(void *param)
|
||||
{
|
||||
const test_task_param_t *test_task_param = param;
|
||||
vTaskDelay(test_task_param->delay_ms / portTICK_PERIOD_MS);
|
||||
write(test_task_param->fd, message, sizeof(message));
|
||||
if (test_task_param->sem) {
|
||||
xSemaphoreGive(test_task_param->sem);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static inline void start_task(const test_task_param_t *test_task_param)
|
||||
{
|
||||
xTaskCreate(send_task, "send_task", 4*1024, (void *) test_task_param, 5, NULL);
|
||||
}
|
||||
|
||||
static void init(int *uart_fd, int *socket_fd)
|
||||
{
|
||||
test_case_uses_tcpip();
|
||||
|
||||
uart1_init();
|
||||
uart_set_loop_back(UART_NUM_1, true);
|
||||
|
||||
*uart_fd = open("/dev/uart/1", O_RDWR);
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(*uart_fd, -1, "Cannot open UART");
|
||||
|
||||
esp_vfs_dev_uart_use_driver(1);
|
||||
|
||||
*socket_fd = socket_init();
|
||||
}
|
||||
|
||||
static void deinit(int uart_fd, int socket_fd)
|
||||
{
|
||||
esp_vfs_dev_uart_use_nonblocking(1);
|
||||
close(uart_fd);
|
||||
uart_driver_delete(UART_NUM_1);
|
||||
|
||||
close(socket_fd);
|
||||
}
|
||||
|
||||
TEST_CASE("UART can do select()", "[vfs]")
|
||||
{
|
||||
int uart_fd;
|
||||
int socket_fd;
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 100000,
|
||||
};
|
||||
char recv_message[sizeof(message)];
|
||||
|
||||
init(&uart_fd, &socket_fd);
|
||||
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(uart_fd, &rfds);
|
||||
//without socket in rfds it will not use the same signalization
|
||||
|
||||
const test_task_param_t test_task_param = {
|
||||
.fd = uart_fd,
|
||||
.delay_ms = 50,
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(test_task_param.sem);
|
||||
start_task(&test_task_param);
|
||||
|
||||
int s = select(uart_fd + 1, &rfds, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(s, 1);
|
||||
TEST_ASSERT(FD_ISSET(uart_fd, &rfds));
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
|
||||
|
||||
int read_bytes = read(uart_fd, recv_message, sizeof(message));
|
||||
TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
|
||||
TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(uart_fd, &rfds);
|
||||
FD_SET(socket_fd, &rfds);
|
||||
|
||||
start_task(&test_task_param);
|
||||
|
||||
s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(s, 1);
|
||||
TEST_ASSERT(FD_ISSET(uart_fd, &rfds));
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
|
||||
|
||||
read_bytes = read(uart_fd, recv_message, sizeof(message));
|
||||
TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
|
||||
TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
vSemaphoreDelete(test_task_param.sem);
|
||||
|
||||
deinit(uart_fd, socket_fd);
|
||||
}
|
||||
|
||||
TEST_CASE("UART can do poll()", "[vfs]")
|
||||
{
|
||||
int uart_fd;
|
||||
int socket_fd;
|
||||
char recv_message[sizeof(message)];
|
||||
|
||||
init(&uart_fd, &socket_fd);
|
||||
|
||||
struct pollfd poll_fds[] = {
|
||||
{
|
||||
.fd = uart_fd,
|
||||
.events = POLLIN,
|
||||
},
|
||||
{
|
||||
.fd = -1, // should be ignored according to the documentation of poll()
|
||||
},
|
||||
};
|
||||
|
||||
const test_task_param_t test_task_param = {
|
||||
.fd = uart_fd,
|
||||
.delay_ms = 50,
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(test_task_param.sem);
|
||||
start_task(&test_task_param);
|
||||
|
||||
int s = poll(poll_fds, sizeof(poll_fds)/sizeof(poll_fds[0]), 100);
|
||||
TEST_ASSERT_EQUAL(s, 1);
|
||||
TEST_ASSERT_EQUAL(uart_fd, poll_fds[0].fd);
|
||||
TEST_ASSERT_EQUAL(POLLIN, poll_fds[0].revents);
|
||||
TEST_ASSERT_EQUAL(-1, poll_fds[1].fd);
|
||||
TEST_ASSERT_EQUAL(0, poll_fds[1].revents);
|
||||
|
||||
int read_bytes = read(uart_fd, recv_message, sizeof(message));
|
||||
TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
|
||||
TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
|
||||
poll_fds[1].fd = socket_fd;
|
||||
poll_fds[1].events = POLLIN;
|
||||
|
||||
start_task(&test_task_param);
|
||||
|
||||
s = poll(poll_fds, sizeof(poll_fds)/sizeof(poll_fds[0]), 100);
|
||||
TEST_ASSERT_EQUAL(s, 1);
|
||||
TEST_ASSERT_EQUAL(uart_fd, poll_fds[0].fd);
|
||||
TEST_ASSERT_EQUAL(POLLIN, poll_fds[0].revents);
|
||||
TEST_ASSERT_EQUAL(socket_fd, poll_fds[1].fd);
|
||||
TEST_ASSERT_EQUAL(0, poll_fds[1].revents);
|
||||
|
||||
read_bytes = read(uart_fd, recv_message, sizeof(message));
|
||||
TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
|
||||
TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
vSemaphoreDelete(test_task_param.sem);
|
||||
|
||||
deinit(uart_fd, socket_fd);
|
||||
}
|
||||
|
||||
TEST_CASE("socket can do select()", "[vfs]")
|
||||
{
|
||||
int uart_fd;
|
||||
int socket_fd;
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 100000,
|
||||
};
|
||||
char recv_message[sizeof(message)];
|
||||
|
||||
init(&uart_fd, &socket_fd);
|
||||
const int dummy_socket_fd = open_dummy_socket();
|
||||
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(uart_fd, &rfds);
|
||||
FD_SET(socket_fd, &rfds);
|
||||
FD_SET(dummy_socket_fd, &rfds);
|
||||
|
||||
const test_task_param_t test_task_param = {
|
||||
.fd = socket_fd,
|
||||
.delay_ms = 50,
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(test_task_param.sem);
|
||||
start_task(&test_task_param);
|
||||
|
||||
const int s = select(MAX(MAX(uart_fd, socket_fd), dummy_socket_fd) + 1, &rfds, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(1, s);
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(uart_fd, &rfds));
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(dummy_socket_fd, &rfds));
|
||||
TEST_ASSERT(FD_ISSET(socket_fd, &rfds));
|
||||
|
||||
int read_bytes = read(socket_fd, recv_message, sizeof(message));
|
||||
TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
|
||||
TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
vSemaphoreDelete(test_task_param.sem);
|
||||
|
||||
deinit(uart_fd, socket_fd);
|
||||
close(dummy_socket_fd);
|
||||
}
|
||||
|
||||
TEST_CASE("socket can do poll()", "[vfs]")
|
||||
{
|
||||
int uart_fd;
|
||||
int socket_fd;
|
||||
char recv_message[sizeof(message)];
|
||||
|
||||
init(&uart_fd, &socket_fd);
|
||||
const int dummy_socket_fd = open_dummy_socket();
|
||||
|
||||
struct pollfd poll_fds[] = {
|
||||
{
|
||||
.fd = uart_fd,
|
||||
.events = POLLIN,
|
||||
},
|
||||
{
|
||||
.fd = socket_fd,
|
||||
.events = POLLIN,
|
||||
},
|
||||
{
|
||||
.fd = dummy_socket_fd,
|
||||
.events = POLLIN,
|
||||
},
|
||||
};
|
||||
|
||||
const test_task_param_t test_task_param = {
|
||||
.fd = socket_fd,
|
||||
.delay_ms = 50,
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(test_task_param.sem);
|
||||
start_task(&test_task_param);
|
||||
|
||||
int s = poll(poll_fds, sizeof(poll_fds)/sizeof(poll_fds[0]), 100);
|
||||
TEST_ASSERT_EQUAL(s, 1);
|
||||
TEST_ASSERT_EQUAL(uart_fd, poll_fds[0].fd);
|
||||
TEST_ASSERT_EQUAL(0, poll_fds[0].revents);
|
||||
TEST_ASSERT_EQUAL(socket_fd, poll_fds[1].fd);
|
||||
TEST_ASSERT_EQUAL(POLLIN, poll_fds[1].revents);
|
||||
TEST_ASSERT_EQUAL(dummy_socket_fd, poll_fds[2].fd);
|
||||
TEST_ASSERT_EQUAL(0, poll_fds[2].revents);
|
||||
|
||||
int read_bytes = read(socket_fd, recv_message, sizeof(message));
|
||||
TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
|
||||
TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
vSemaphoreDelete(test_task_param.sem);
|
||||
|
||||
deinit(uart_fd, socket_fd);
|
||||
close(dummy_socket_fd);
|
||||
}
|
||||
|
||||
TEST_CASE("select() timeout", "[vfs]")
|
||||
{
|
||||
int uart_fd;
|
||||
int socket_fd;
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 100000,
|
||||
};
|
||||
|
||||
init(&uart_fd, &socket_fd);
|
||||
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(uart_fd, &rfds);
|
||||
FD_SET(socket_fd, &rfds);
|
||||
|
||||
int s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(s, 0);
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(uart_fd, &rfds));
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
|
||||
s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(s, 0);
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(uart_fd, &rfds));
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
|
||||
|
||||
deinit(uart_fd, socket_fd);
|
||||
}
|
||||
|
||||
TEST_CASE("poll() timeout", "[vfs]")
|
||||
{
|
||||
int uart_fd;
|
||||
int socket_fd;
|
||||
|
||||
init(&uart_fd, &socket_fd);
|
||||
|
||||
struct pollfd poll_fds[] = {
|
||||
{
|
||||
.fd = uart_fd,
|
||||
.events = POLLIN,
|
||||
},
|
||||
{
|
||||
.fd = socket_fd,
|
||||
.events = POLLIN,
|
||||
},
|
||||
};
|
||||
|
||||
int s = poll(poll_fds, sizeof(poll_fds)/sizeof(poll_fds[0]), 100);
|
||||
TEST_ASSERT_EQUAL(s, 0);
|
||||
TEST_ASSERT_EQUAL(uart_fd, poll_fds[0].fd);
|
||||
TEST_ASSERT_EQUAL(0, poll_fds[0].revents);
|
||||
TEST_ASSERT_EQUAL(socket_fd, poll_fds[1].fd);
|
||||
TEST_ASSERT_EQUAL(0, poll_fds[1].revents);
|
||||
|
||||
poll_fds[0].fd = -1;
|
||||
poll_fds[1].fd = -1;
|
||||
|
||||
s = poll(poll_fds, sizeof(poll_fds)/sizeof(poll_fds[0]), 100);
|
||||
TEST_ASSERT_EQUAL(s, 0);
|
||||
TEST_ASSERT_EQUAL(-1, poll_fds[0].fd);
|
||||
TEST_ASSERT_EQUAL(0, poll_fds[0].revents);
|
||||
TEST_ASSERT_EQUAL(-1, poll_fds[1].fd);
|
||||
TEST_ASSERT_EQUAL(0, poll_fds[1].revents);
|
||||
|
||||
deinit(uart_fd, socket_fd);
|
||||
}
|
||||
|
||||
static void select_task(void *task_param)
|
||||
{
|
||||
const test_select_task_param_t *param = task_param;
|
||||
|
||||
int s = select(param->maxfds, param->rdfds, param->wrfds, param->errfds, param->tv);
|
||||
TEST_ASSERT_EQUAL(param->select_ret, s);
|
||||
|
||||
if (param->sem) {
|
||||
xSemaphoreGive(param->sem);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void inline start_select_task(test_select_task_param_t *param)
|
||||
{
|
||||
xTaskCreate(select_task, "select_task", 4*1024, (void *) param, 5, NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("concurrent selects work", "[vfs]")
|
||||
{
|
||||
int uart_fd, socket_fd;
|
||||
init(&uart_fd, &socket_fd);
|
||||
const int dummy_socket_fd = open_dummy_socket();
|
||||
|
||||
{
|
||||
// Two tasks will wait for the same UART FD for reading and they will time-out
|
||||
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 100000,
|
||||
};
|
||||
|
||||
fd_set rdfds1;
|
||||
FD_ZERO(&rdfds1);
|
||||
FD_SET(uart_fd, &rdfds1);
|
||||
|
||||
test_select_task_param_t param = {
|
||||
.rdfds = &rdfds1,
|
||||
.wrfds = NULL,
|
||||
.errfds = NULL,
|
||||
.maxfds = uart_fd + 1,
|
||||
.tv = &tv,
|
||||
.select_ret = 0, // expected timeout
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(param.sem);
|
||||
|
||||
fd_set rdfds2;
|
||||
FD_ZERO(&rdfds2);
|
||||
FD_SET(uart_fd, &rdfds2);
|
||||
FD_SET(socket_fd, &rdfds2);
|
||||
FD_SET(dummy_socket_fd, &rdfds2);
|
||||
|
||||
start_select_task(¶m);
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS); //make sure the task has started and waits in select()
|
||||
|
||||
int s = select(MAX(MAX(uart_fd, dummy_socket_fd), socket_fd) + 1, &rdfds2, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(0, s); // timeout here as well
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(param.sem, 1000 / portTICK_PERIOD_MS));
|
||||
vSemaphoreDelete(param.sem);
|
||||
}
|
||||
|
||||
{
|
||||
// One tasks waits for UART reading and one for writing. The former will be successful and latter will
|
||||
// time-out.
|
||||
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 100000,
|
||||
};
|
||||
|
||||
fd_set wrfds1;
|
||||
FD_ZERO(&wrfds1);
|
||||
FD_SET(uart_fd, &wrfds1);
|
||||
|
||||
test_select_task_param_t param = {
|
||||
.rdfds = NULL,
|
||||
.wrfds = &wrfds1,
|
||||
.errfds = NULL,
|
||||
.maxfds = uart_fd + 1,
|
||||
.tv = &tv,
|
||||
.select_ret = 0, // expected timeout
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(param.sem);
|
||||
|
||||
start_select_task(¶m);
|
||||
|
||||
fd_set rdfds2;
|
||||
FD_ZERO(&rdfds2);
|
||||
FD_SET(uart_fd, &rdfds2);
|
||||
FD_SET(socket_fd, &rdfds2);
|
||||
FD_SET(dummy_socket_fd, &rdfds2);
|
||||
|
||||
const test_task_param_t send_param = {
|
||||
.fd = uart_fd,
|
||||
.delay_ms = 50,
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(send_param.sem);
|
||||
start_task(&send_param); // This task will write to UART which will be detected by select()
|
||||
|
||||
int s = select(MAX(MAX(uart_fd, dummy_socket_fd), socket_fd) + 1, &rdfds2, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(1, s);
|
||||
TEST_ASSERT(FD_ISSET(uart_fd, &rdfds2));
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rdfds2));
|
||||
TEST_ASSERT_UNLESS(FD_ISSET(dummy_socket_fd, &rdfds2));
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(param.sem, 1000 / portTICK_PERIOD_MS));
|
||||
vSemaphoreDelete(param.sem);
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(send_param.sem, 1000 / portTICK_PERIOD_MS));
|
||||
vSemaphoreDelete(send_param.sem);
|
||||
}
|
||||
|
||||
deinit(uart_fd, socket_fd);
|
||||
close(dummy_socket_fd);
|
||||
}
|
||||
|
||||
TEST_CASE("select() works with concurrent mount", "[vfs][fatfs]")
|
||||
{
|
||||
wl_handle_t test_wl_handle;
|
||||
int uart_fd, socket_fd;
|
||||
|
||||
init(&uart_fd, &socket_fd);
|
||||
const int dummy_socket_fd = open_dummy_socket();
|
||||
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 2
|
||||
};
|
||||
|
||||
// select() will be waiting for a socket & UART and FATFS mount will occur in parallel
|
||||
|
||||
struct timeval tv = {
|
||||
.tv_sec = 1,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
|
||||
fd_set rdfds;
|
||||
FD_ZERO(&rdfds);
|
||||
FD_SET(uart_fd, &rdfds);
|
||||
FD_SET(dummy_socket_fd, &rdfds);
|
||||
|
||||
test_select_task_param_t param = {
|
||||
.rdfds = &rdfds,
|
||||
.wrfds = NULL,
|
||||
.errfds = NULL,
|
||||
.maxfds = MAX(uart_fd, dummy_socket_fd) + 1,
|
||||
.tv = &tv,
|
||||
.select_ret = 0, // expected timeout
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(param.sem);
|
||||
|
||||
start_select_task(¶m);
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS); //make sure the task has started and waits in select()
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_mount("/spiflash", NULL, &mount_config, &test_wl_handle));
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(param.sem, 1500 / portTICK_PERIOD_MS));
|
||||
|
||||
// select() will be waiting for a socket & UART and FATFS unmount will occur in parallel
|
||||
|
||||
FD_ZERO(&rdfds);
|
||||
FD_SET(uart_fd, &rdfds);
|
||||
FD_SET(dummy_socket_fd, &rdfds);
|
||||
|
||||
start_select_task(¶m);
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS); //make sure the task has started and waits in select()
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount("/spiflash", test_wl_handle));
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(param.sem, 1500 / portTICK_PERIOD_MS));
|
||||
|
||||
vSemaphoreDelete(param.sem);
|
||||
|
||||
deinit(uart_fd, socket_fd);
|
||||
close(dummy_socket_fd);
|
||||
}
|
348
components/vfs/test/test_vfs_uart.c
Normal file
348
components/vfs/test/test_vfs_uart.c
Normal file
@ -0,0 +1,348 @@
|
||||
// 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.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/termios.h>
|
||||
#include <sys/errno.h>
|
||||
#include "unity.h"
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#include "esp32/rom/uart.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2BETA
|
||||
#include "esp32s2beta/rom/uart.h"
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "driver/uart.h"
|
||||
#include "soc/uart_struct.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static void fwrite_str_loopback(const char* str, size_t size)
|
||||
{
|
||||
uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
UART0.conf0.loopback = 1;
|
||||
fwrite(str, 1, size, stdout);
|
||||
fflush(stdout);
|
||||
uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
vTaskDelay(2 / portTICK_PERIOD_MS);
|
||||
UART0.conf0.loopback = 0;
|
||||
}
|
||||
|
||||
static void flush_stdin_stdout(void)
|
||||
{
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
char bitbucket[UART_FIFO_LEN];
|
||||
while (fread(bitbucket, 1, UART_FIFO_LEN, stdin) > 0) {
|
||||
;
|
||||
}
|
||||
fflush(stdout);
|
||||
uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
}
|
||||
|
||||
TEST_CASE("can read from stdin", "[vfs]")
|
||||
{
|
||||
flush_stdin_stdout();
|
||||
|
||||
const size_t count = 12;
|
||||
srand(count);
|
||||
char* data = (char*) calloc(1, count * 8 + 2);
|
||||
char* buf = (char*) calloc(1, count * 8 + 2);
|
||||
char* p = data;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
p += sprintf(p, "%08x", rand());
|
||||
}
|
||||
p += sprintf(p, "\n");
|
||||
size_t len = p - data;
|
||||
fwrite_str_loopback(data, len);
|
||||
size_t cb = fread(buf, 1, len, stdin);
|
||||
TEST_ASSERT_EQUAL(len, cb);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(data, buf, len);
|
||||
|
||||
free(data);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("CRs are removed from the stdin correctly", "[vfs]")
|
||||
{
|
||||
esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
flush_stdin_stdout();
|
||||
const char* send_str = "1234567890\n\r123\r\n4\n";
|
||||
/* with CONFIG_NEWLIB_STDOUT_ADDCR, the following will be sent on the wire.
|
||||
* (last character of each part is marked with a hat)
|
||||
*
|
||||
* 1234567890\r\n\r123\r\r\n4\r\n
|
||||
* ^ ^^ ^
|
||||
*/
|
||||
char buf[128];
|
||||
char* dst = buf;
|
||||
|
||||
fwrite_str_loopback(send_str, 11); // send up to the first \n
|
||||
size_t rb = fread(dst, 1, 5, stdin); // read first 5
|
||||
TEST_ASSERT_EQUAL(5, rb);
|
||||
dst += rb;
|
||||
|
||||
rb = fread(dst, 1, 6, stdin); // ask for 6
|
||||
TEST_ASSERT_EQUAL(6, rb); // get 6
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY("1234567890\n", buf, 11);
|
||||
dst += rb;
|
||||
|
||||
rb = fread(dst, 1, 2, stdin); // any more characters?
|
||||
TEST_ASSERT_EQUAL(0, rb); // nothing
|
||||
|
||||
fwrite_str_loopback(send_str + 11, 1); // send the '\r'
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
rb = fread(dst, 1, 2, stdin); // try to get somthing
|
||||
TEST_ASSERT_EQUAL(0, rb); // still nothing (\r is buffered)
|
||||
|
||||
fwrite_str_loopback(send_str + 12, 1); // Now send the '1'
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
rb = fread(dst, 1, 2, stdin); // try again
|
||||
TEST_ASSERT_EQUAL(2, rb); // get two characters
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY("\r1", dst, 2);
|
||||
dst += rb;
|
||||
|
||||
fwrite_str_loopback(send_str + 13, 6); // Send the rest
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
rb = fread(dst, 1, 4, stdin); // consume "23\r\n"
|
||||
TEST_ASSERT_EQUAL(4, rb);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY("23\r\n", dst, 4);
|
||||
dst += rb;
|
||||
|
||||
rb = fread(dst, 1, 4, stdin); // ask for more than the remainder
|
||||
TEST_ASSERT_EQUAL(2, rb);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY("4\n", dst, 2);
|
||||
}
|
||||
|
||||
TEST_CASE("can write to UART while another task is reading", "[vfs]")
|
||||
{
|
||||
struct read_task_arg_t {
|
||||
char* out_buffer;
|
||||
size_t out_buffer_len;
|
||||
SemaphoreHandle_t ready;
|
||||
SemaphoreHandle_t done;
|
||||
};
|
||||
|
||||
struct write_task_arg_t {
|
||||
const char* str;
|
||||
SemaphoreHandle_t done;
|
||||
};
|
||||
|
||||
void read_task_fn(void* varg)
|
||||
{
|
||||
struct read_task_arg_t* parg = (struct read_task_arg_t*) varg;
|
||||
parg->out_buffer[0] = 0;
|
||||
|
||||
fgets(parg->out_buffer, parg->out_buffer_len, stdin);
|
||||
xSemaphoreGive(parg->done);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void write_task_fn(void* varg)
|
||||
{
|
||||
struct write_task_arg_t* parg = (struct write_task_arg_t*) varg;
|
||||
fwrite_str_loopback(parg->str, strlen(parg->str));
|
||||
xSemaphoreGive(parg->done);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
char out_buffer[32];
|
||||
size_t out_buffer_len = sizeof(out_buffer);
|
||||
|
||||
struct read_task_arg_t read_arg = {
|
||||
.out_buffer = out_buffer,
|
||||
.out_buffer_len = out_buffer_len,
|
||||
.done = xSemaphoreCreateBinary()
|
||||
};
|
||||
|
||||
struct write_task_arg_t write_arg = {
|
||||
.str = "!(@*#&(!*@&#((SDasdkjhadsl\n",
|
||||
.done = xSemaphoreCreateBinary()
|
||||
};
|
||||
|
||||
flush_stdin_stdout();
|
||||
|
||||
ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM,
|
||||
256, 0, 0, NULL, 0) );
|
||||
esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
|
||||
|
||||
xTaskCreate(&read_task_fn, "vfs_read", 4096, &read_arg, 5, NULL);
|
||||
vTaskDelay(10);
|
||||
xTaskCreate(&write_task_fn, "vfs_write", 4096, &write_arg, 6, NULL);
|
||||
|
||||
|
||||
int res = xSemaphoreTake(write_arg.done, 100 / portTICK_PERIOD_MS);
|
||||
TEST_ASSERT(res);
|
||||
|
||||
res = xSemaphoreTake(read_arg.done, 100 / portTICK_PERIOD_MS);
|
||||
TEST_ASSERT(res);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, strcmp(write_arg.str, read_arg.out_buffer));
|
||||
|
||||
esp_vfs_dev_uart_use_nonblocking(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
uart_driver_delete(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
vSemaphoreDelete(read_arg.done);
|
||||
vSemaphoreDelete(write_arg.done);
|
||||
}
|
||||
|
||||
TEST_CASE("fcntl supported in UART VFS", "[vfs]")
|
||||
{
|
||||
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, flags);
|
||||
int res = fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, res);
|
||||
/* revert */
|
||||
res = fcntl(STDIN_FILENO, F_SETFL, flags);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, res);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
|
||||
TEST_CASE("Can use termios for UART", "[vfs]")
|
||||
{
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 115200,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_APB,
|
||||
};
|
||||
uart_driver_install(UART_NUM_1, 256, 256, 0, NULL, 0);
|
||||
uart_param_config(UART_NUM_1, &uart_config);
|
||||
|
||||
const int uart_fd = open("/dev/uart/1", O_RDWR);
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(uart_fd, -1, "Cannot open UART");
|
||||
esp_vfs_dev_uart_use_driver(1);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, tcgetattr(uart_fd, NULL));
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
|
||||
struct termios tios, tios_result;
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, tcgetattr(-1, &tios));
|
||||
TEST_ASSERT_EQUAL(EBADF, errno);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios));
|
||||
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSADRAIN, &tios));
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSAFLUSH, &tios));
|
||||
|
||||
tios.c_iflag |= IGNCR;
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios));
|
||||
tios.c_iflag &= (~IGNCR);
|
||||
TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result));
|
||||
TEST_ASSERT_EQUAL(IGNCR, tios_result.c_iflag & IGNCR);
|
||||
memset(&tios_result, 0xFF, sizeof(struct termios));
|
||||
|
||||
tios.c_iflag |= ICRNL;
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios));
|
||||
tios.c_iflag &= (~ICRNL);
|
||||
TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result));
|
||||
TEST_ASSERT_EQUAL(ICRNL, tios_result.c_iflag & ICRNL);
|
||||
memset(&tios_result, 0xFF, sizeof(struct termios));
|
||||
|
||||
{
|
||||
uart_word_length_t data_bit;
|
||||
uart_stop_bits_t stop_bits;
|
||||
uart_parity_t parity_mode;
|
||||
|
||||
tios.c_cflag &= (~CSIZE);
|
||||
tios.c_cflag &= (~CSTOPB);
|
||||
tios.c_cflag &= (~PARENB);
|
||||
tios.c_cflag |= CS6;
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios));
|
||||
tios.c_cflag &= (~CSIZE);
|
||||
TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result));
|
||||
TEST_ASSERT_EQUAL(CS6, tios_result.c_cflag & CS6);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, uart_get_word_length(UART_NUM_1, &data_bit));
|
||||
TEST_ASSERT_EQUAL(UART_DATA_6_BITS, data_bit);
|
||||
TEST_ASSERT_EQUAL(0, tios_result.c_cflag & CSTOPB);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, uart_get_stop_bits(UART_NUM_1, &stop_bits));
|
||||
TEST_ASSERT_EQUAL(UART_STOP_BITS_1, stop_bits);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, uart_get_parity(UART_NUM_1, &parity_mode));
|
||||
TEST_ASSERT_EQUAL(UART_PARITY_DISABLE, parity_mode);
|
||||
memset(&tios_result, 0xFF, sizeof(struct termios));
|
||||
}
|
||||
|
||||
{
|
||||
uart_stop_bits_t stop_bits;
|
||||
uart_parity_t parity_mode;
|
||||
|
||||
tios.c_cflag |= CSTOPB;
|
||||
tios.c_cflag |= (PARENB | PARODD);
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios));
|
||||
tios.c_cflag &= (~(CSTOPB | PARENB | PARODD));
|
||||
TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result));
|
||||
TEST_ASSERT_EQUAL(CSTOPB, tios_result.c_cflag & CSTOPB);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, uart_get_stop_bits(UART_NUM_1, &stop_bits));
|
||||
TEST_ASSERT_EQUAL(UART_STOP_BITS_2, stop_bits);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, uart_get_parity(UART_NUM_1, &parity_mode));
|
||||
TEST_ASSERT_EQUAL(UART_PARITY_ODD, parity_mode);
|
||||
memset(&tios_result, 0xFF, sizeof(struct termios));
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t baudrate;
|
||||
|
||||
tios.c_cflag &= (~BOTHER);
|
||||
tios.c_cflag |= CBAUD;
|
||||
tios.c_ispeed = tios.c_ospeed = B38400;
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios));
|
||||
TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result));
|
||||
TEST_ASSERT_EQUAL(CBAUD, tios_result.c_cflag & CBAUD);
|
||||
TEST_ASSERT_EQUAL(B38400, tios_result.c_ispeed);
|
||||
TEST_ASSERT_EQUAL(B38400, tios_result.c_ospeed);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, uart_get_baudrate(UART_NUM_1, &baudrate));
|
||||
TEST_ASSERT_EQUAL(38400, baudrate);
|
||||
|
||||
tios.c_cflag |= CBAUDEX;
|
||||
tios.c_ispeed = tios.c_ospeed = B230400;
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios));
|
||||
TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result));
|
||||
TEST_ASSERT_EQUAL(BOTHER, tios_result.c_cflag & BOTHER);
|
||||
// Setting the speed to 230400 will set it actually to 230423
|
||||
TEST_ASSERT_EQUAL(230423, tios_result.c_ispeed);
|
||||
TEST_ASSERT_EQUAL(230423, tios_result.c_ospeed);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, uart_get_baudrate(UART_NUM_1, &baudrate));
|
||||
TEST_ASSERT_EQUAL(230423, baudrate);
|
||||
|
||||
tios.c_cflag |= BOTHER;
|
||||
tios.c_ispeed = tios.c_ospeed = 213;
|
||||
TEST_ASSERT_EQUAL(0, tcsetattr(uart_fd, TCSANOW, &tios));
|
||||
TEST_ASSERT_EQUAL(0, tcgetattr(uart_fd, &tios_result));
|
||||
TEST_ASSERT_EQUAL(BOTHER, tios_result.c_cflag & BOTHER);
|
||||
TEST_ASSERT_EQUAL(213, tios_result.c_ispeed);
|
||||
TEST_ASSERT_EQUAL(213, tios_result.c_ospeed);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, uart_get_baudrate(UART_NUM_1, &baudrate));
|
||||
TEST_ASSERT_EQUAL(213, baudrate);
|
||||
|
||||
memset(&tios_result, 0xFF, sizeof(struct termios));
|
||||
}
|
||||
|
||||
esp_vfs_dev_uart_use_nonblocking(1);
|
||||
close(uart_fd);
|
||||
uart_driver_delete(UART_NUM_1);
|
||||
}
|
||||
#endif // CONFIG_VFS_SUPPORT_TERMIOS
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
// Copyright 2015-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.
|
||||
@ -27,9 +27,9 @@
|
||||
#include "esp_vfs.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT
|
||||
#ifdef CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_NONE
|
||||
#endif //CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT
|
||||
#endif //CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "vfs";
|
||||
@ -447,6 +447,33 @@ ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t esp_vfs_pread(int fd, void *dst, size_t size, off_t offset)
|
||||
{
|
||||
struct _reent *r = __getreent();
|
||||
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, pread, local_fd, dst, size, offset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t esp_vfs_pwrite(int fd, const void *src, size_t size, off_t offset)
|
||||
{
|
||||
struct _reent *r = __getreent();
|
||||
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, pwrite, local_fd, src, size, offset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int esp_vfs_close(struct _reent *r, int fd)
|
||||
{
|
||||
@ -544,6 +571,34 @@ int esp_vfs_rename(struct _reent *r, const char *src, const char *dst)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Create aliases for newlib syscalls
|
||||
|
||||
These functions are also available in ROM as stubs which use the syscall table, but linking them
|
||||
directly here saves an additional function call when a software function is linked to one, and
|
||||
makes linking with -stdlib easier.
|
||||
*/
|
||||
int _open_r(struct _reent *r, const char * path, int flags, int mode)
|
||||
__attribute__((alias("esp_vfs_open")));
|
||||
ssize_t _write_r(struct _reent *r, int fd, const void * data, size_t size)
|
||||
__attribute__((alias("esp_vfs_write")));
|
||||
off_t _lseek_r(struct _reent *r, int fd, off_t size, int mode)
|
||||
__attribute__((alias("esp_vfs_lseek")));
|
||||
ssize_t _read_r(struct _reent *r, int fd, void * dst, size_t size)
|
||||
__attribute__((alias("esp_vfs_read")));
|
||||
int _close_r(struct _reent *r, int fd)
|
||||
__attribute__((alias("esp_vfs_close")));
|
||||
int _fstat_r(struct _reent *r, int fd, struct stat * st)
|
||||
__attribute__((alias("esp_vfs_fstat")));
|
||||
int _stat_r(struct _reent *r, const char * path, struct stat * st)
|
||||
__attribute__((alias("esp_vfs_stat")));
|
||||
int _link_r(struct _reent *r, const char* n1, const char* n2)
|
||||
__attribute__((alias("esp_vfs_link")));
|
||||
int _unlink_r(struct _reent *r, const char *path)
|
||||
__attribute__((alias("esp_vfs_unlink")));
|
||||
int _rename_r(struct _reent *r, const char *src, const char *dst)
|
||||
__attribute__((alias("esp_vfs_rename")));
|
||||
|
||||
|
||||
DIR* opendir(const char* name)
|
||||
{
|
||||
const vfs_entry_t* vfs = get_vfs_for_path(name);
|
||||
@ -657,20 +712,16 @@ int rmdir(const char* name)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fcntl(int fd, int cmd, ...)
|
||||
int _fcntl_r(struct _reent *r, int fd, int cmd, int arg)
|
||||
{
|
||||
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);
|
||||
CHECK_AND_CALL(ret, r, vfs, fcntl, local_fd, cmd, arg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -733,13 +784,16 @@ int truncate(const char *path, off_t length)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void call_end_selects(int end_index, const fds_triple_t *vfs_fds_triple)
|
||||
static void call_end_selects(int end_index, const fds_triple_t *vfs_fds_triple, void **driver_args)
|
||||
{
|
||||
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();
|
||||
esp_err_t err = vfs->vfs.end_select(driver_args[i]);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGD(TAG, "end_select failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -794,12 +848,14 @@ static void esp_vfs_log_fd_set(const char *fds_name, const fd_set *fds)
|
||||
|
||||
int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)
|
||||
{
|
||||
// NOTE: Please see the "Synchronous input/output multiplexing" section of the ESP-IDF Programming Guide
|
||||
// (API Reference -> Storage -> Virtual Filesystem) for a general overview of the implementation of VFS select().
|
||||
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_LOGD(TAG, "timeout is %lds + %ldus", (long)timeout->tv_sec, timeout->tv_usec);
|
||||
}
|
||||
esp_vfs_log_fd_set("readfds", readfds);
|
||||
esp_vfs_log_fd_set("writefds", writefds);
|
||||
@ -811,13 +867,22 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Capture s_vfs_count to a local variable in case a new driver is registered or removed during this actual select()
|
||||
// call. s_vfs_count cannot be protected with a mutex during a select() call (which can be one without a timeout)
|
||||
// because that could block the registration of new driver.
|
||||
const size_t vfs_count = s_vfs_count;
|
||||
fds_triple_t *vfs_fds_triple;
|
||||
if ((vfs_fds_triple = calloc(s_vfs_count, sizeof(fds_triple_t))) == NULL) {
|
||||
if ((vfs_fds_triple = calloc(vfs_count, sizeof(fds_triple_t))) == NULL) {
|
||||
__errno_r(r) = ENOMEM;
|
||||
ESP_LOGD(TAG, "calloc is unsuccessful");
|
||||
return -1;
|
||||
}
|
||||
|
||||
esp_vfs_select_sem_t sel_sem = {
|
||||
.is_sem_local = false,
|
||||
.sem = NULL,
|
||||
};
|
||||
|
||||
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);
|
||||
@ -838,6 +903,7 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds
|
||||
esp_vfs_safe_fd_isset(fd, errorfds)) {
|
||||
const vfs_entry_t *vfs = s_vfs[vfs_index];
|
||||
socket_select = vfs->vfs.socket_select;
|
||||
sel_sem.sem = vfs->vfs.get_socket_select_semaphore();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
@ -868,24 +934,28 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds
|
||||
// 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) {
|
||||
sel_sem.is_sem_local = true;
|
||||
if ((sel_sem.sem = xSemaphoreCreateBinary()) == NULL) {
|
||||
free(vfs_fds_triple);
|
||||
__errno_r(r) = ENOMEM;
|
||||
ESP_LOGD(TAG, "cannot create select_sem");
|
||||
ESP_LOGD(TAG, "cannot create select semaphore");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < s_vfs_count; ++i) {
|
||||
void **driver_args = calloc(vfs_count, sizeof(void *));
|
||||
|
||||
if (driver_args == NULL) {
|
||||
free(vfs_fds_triple);
|
||||
__errno_r(r) = ENOMEM;
|
||||
ESP_LOGD(TAG, "calloc is unsuccessful for driver args");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vfs_count; ++i) {
|
||||
const vfs_entry_t *vfs = get_vfs_for_index(i);
|
||||
fds_triple_t *item = &vfs_fds_triple[i];
|
||||
|
||||
@ -896,18 +966,20 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds
|
||||
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);
|
||||
esp_err_t err = vfs->vfs.start_select(nfds, &item->readfds, &item->writefds, &item->errorfds, sel_sem,
|
||||
driver_args + i);
|
||||
|
||||
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;
|
||||
call_end_selects(i, vfs_fds_triple, driver_args);
|
||||
(void) set_global_fd_sets(vfs_fds_triple, vfs_count, readfds, writefds, errorfds);
|
||||
if (sel_sem.is_sem_local && sel_sem.sem) {
|
||||
vSemaphoreDelete(sel_sem.sem);
|
||||
sel_sem.sem = NULL;
|
||||
}
|
||||
free(vfs_fds_triple);
|
||||
free(driver_args);
|
||||
__errno_r(r) = EINTR;
|
||||
ESP_LOGD(TAG, "start_select failed");
|
||||
ESP_LOGD(TAG, "start_select failed: %s", esp_err_to_name(err));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -941,18 +1013,19 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds
|
||||
ESP_LOGD(TAG, "timeout is %dms", timeout_ms);
|
||||
}
|
||||
ESP_LOGD(TAG, "waiting without calling socket_select");
|
||||
xSemaphoreTake(select_sem, ticks_to_wait);
|
||||
xSemaphoreTake(sel_sem.sem, ticks_to_wait);
|
||||
}
|
||||
|
||||
call_end_selects(s_vfs_count, vfs_fds_triple); // for VFSs for start_select was called before
|
||||
call_end_selects(vfs_count, vfs_fds_triple, driver_args); // 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);
|
||||
ret += set_global_fd_sets(vfs_fds_triple, vfs_count, readfds, writefds, errorfds);
|
||||
}
|
||||
if (select_sem) {
|
||||
vSemaphoreDelete(select_sem);
|
||||
select_sem = NULL;
|
||||
if (sel_sem.is_sem_local && sel_sem.sem) {
|
||||
vSemaphoreDelete(sel_sem.sem);
|
||||
sel_sem.sem = NULL;
|
||||
}
|
||||
free(vfs_fds_triple);
|
||||
free(driver_args);
|
||||
|
||||
ESP_LOGD(TAG, "esp_vfs_select returns %d", ret);
|
||||
esp_vfs_log_fd_set("readfds", readfds);
|
||||
@ -961,43 +1034,47 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds
|
||||
return ret;
|
||||
}
|
||||
|
||||
void esp_vfs_select_triggered(SemaphoreHandle_t *signal_sem)
|
||||
void esp_vfs_select_triggered(esp_vfs_select_sem_t sem)
|
||||
{
|
||||
if (signal_sem && (*signal_sem)) {
|
||||
xSemaphoreGive(*signal_sem);
|
||||
if (sem.is_sem_local) {
|
||||
xSemaphoreGive(sem.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) {
|
||||
// Note: s_vfs_count could have changed since the start of vfs_select() call. However, that change doesn't
|
||||
// matter here stop_socket_select() will be called for only valid VFS drivers.
|
||||
const vfs_entry_t *vfs = s_vfs[i];
|
||||
if (vfs != NULL && vfs->vfs.stop_socket_select != NULL) {
|
||||
vfs->vfs.stop_socket_select();
|
||||
vfs->vfs.stop_socket_select(sem.sem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void esp_vfs_select_triggered_isr(SemaphoreHandle_t *signal_sem, BaseType_t *woken)
|
||||
void esp_vfs_select_triggered_isr(esp_vfs_select_sem_t sem, BaseType_t *woken)
|
||||
{
|
||||
if (signal_sem && (*signal_sem)) {
|
||||
xSemaphoreGiveFromISR(*signal_sem, woken);
|
||||
if (sem.is_sem_local) {
|
||||
xSemaphoreGiveFromISR(sem.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) {
|
||||
// Note: s_vfs_count could have changed since the start of vfs_select() call. However, that change doesn't
|
||||
// matter here stop_socket_select() will be called for only valid VFS drivers.
|
||||
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);
|
||||
vfs->vfs.stop_socket_select_isr(sem.sem, woken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SUPPORT_TERMIOS
|
||||
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
|
||||
int tcgetattr(int fd, struct termios *p)
|
||||
{
|
||||
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
|
||||
@ -1095,9 +1172,23 @@ int tcsendbreak(int fd, int duration)
|
||||
CHECK_AND_CALL(ret, r, vfs, tcsendbreak, local_fd, duration);
|
||||
return ret;
|
||||
}
|
||||
#endif // CONFIG_SUPPORT_TERMIOS
|
||||
#endif // CONFIG_VFS_SUPPORT_TERMIOS
|
||||
|
||||
int esp_vfs_poll(struct pollfd *fds, int nfds, int timeout)
|
||||
int esp_vfs_utime(const char *path, const struct utimbuf *times)
|
||||
{
|
||||
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, utime, path_within_vfs, times);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int esp_vfs_poll(struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
{
|
||||
struct timeval tv = {
|
||||
// timeout is in milliseconds
|
||||
@ -1173,3 +1264,7 @@ int esp_vfs_poll(struct pollfd *fds, int nfds, int timeout)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void vfs_include_syscalls_impl(void)
|
||||
{
|
||||
// Linker hook function, exists to make the linker examine this fine
|
||||
}
|
||||
|
220
components/vfs/vfs_semihost.c
Normal file
220
components/vfs/vfs_semihost.c
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright 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.
|
||||
|
||||
#if 0
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include "soc/cpu.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_vfs.h"
|
||||
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_NONE
|
||||
#include "esp_log.h"
|
||||
const static char *TAG = "esp_semihost";
|
||||
|
||||
#define SYSCALL_INSTR "break 1,1\n"
|
||||
#define SYS_OPEN 0x01
|
||||
#define SYS_CLOSE 0x02
|
||||
#define SYS_WRITE 0x05
|
||||
#define SYS_READ 0x06
|
||||
#define SYS_SEEK 0x0A
|
||||
|
||||
/** ESP-specific file open flag. Indicates that path passed to open() is absolute host path. */
|
||||
#define ESP_O_SEMIHOST_ABSPATH 0x80000000
|
||||
|
||||
typedef struct {
|
||||
char base_path[ESP_VFS_PATH_MAX+1]; /* base path in VFS where host semohosting dir is mounted */
|
||||
char host_path[CONFIG_SEMIHOSTFS_HOST_PATH_MAX_LEN+1]; /* host path to use as base dir for open files */
|
||||
} vfs_semihost_ctx_t;
|
||||
|
||||
static vfs_semihost_ctx_t s_semhost_ctx[CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS];
|
||||
|
||||
|
||||
static inline int generic_syscall(int sys_nr, int arg1, int arg2, int arg3, int arg4, int* ret_errno)
|
||||
{
|
||||
int host_ret, host_errno;
|
||||
|
||||
if (!esp_cpu_in_ocd_debug_mode()) {
|
||||
*ret_errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
__asm__ volatile (
|
||||
"mov a2, %[sys_nr]\n" \
|
||||
"mov a3, %[arg1]\n" \
|
||||
"mov a4, %[arg2]\n" \
|
||||
"mov a5, %[arg3]\n" \
|
||||
"mov a6, %[arg4]\n" \
|
||||
SYSCALL_INSTR \
|
||||
"mov %[host_ret], a2\n" \
|
||||
"mov %[host_errno], a3\n" \
|
||||
:[host_ret]"=r"(host_ret),[host_errno]"=r"(host_errno)
|
||||
:[sys_nr]"r"(sys_nr),[arg1]"r"(arg1),[arg2]"r"(arg2),[arg3]"r"(arg3),[arg4]"r"(arg4)
|
||||
:"a2","a3","a4","a5","a6");
|
||||
*ret_errno = host_errno;
|
||||
return host_ret;
|
||||
}
|
||||
|
||||
inline bool ctx_is_unused(const vfs_semihost_ctx_t* ctx)
|
||||
{
|
||||
return ctx->base_path[0] == 0;
|
||||
}
|
||||
|
||||
inline bool ctx_uses_abspath(const vfs_semihost_ctx_t* ctx)
|
||||
{
|
||||
return ctx->host_path[0];
|
||||
}
|
||||
|
||||
static int vfs_semihost_open(void* ctx, const char * path, int flags, int mode)
|
||||
{
|
||||
int fd = -1, host_err = 0;
|
||||
char *host_path;
|
||||
vfs_semihost_ctx_t *semi_ctx = ctx;
|
||||
|
||||
ESP_LOGV(TAG, "%s: %p '%s 0x%x 0x%x'", __func__, semi_ctx, path, flags, mode);
|
||||
if (ctx_uses_abspath(semi_ctx)) {
|
||||
flags |= ESP_O_SEMIHOST_ABSPATH;
|
||||
host_path = malloc(strlen(semi_ctx->host_path)+strlen(path)+1);
|
||||
if(host_path == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
strcpy(host_path, semi_ctx->host_path);
|
||||
strcat(host_path, path);
|
||||
} else {
|
||||
host_path = (char *)path;
|
||||
}
|
||||
fd = generic_syscall(SYS_OPEN, (int)host_path, strlen(host_path), flags, mode, &host_err);
|
||||
if (ctx_uses_abspath(semi_ctx)) {
|
||||
free(host_path);
|
||||
}
|
||||
if (fd == -1) {
|
||||
errno = host_err;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static ssize_t vfs_semihost_write(void* ctx, int fd, const void * data, size_t size)
|
||||
{
|
||||
int host_err = 0;
|
||||
size_t ret = -1;
|
||||
|
||||
ESP_LOGV(TAG, "%s: %d %u bytes", __func__, fd, size);
|
||||
ret = generic_syscall(SYS_WRITE, fd, (int)data, size, 0, &host_err);
|
||||
if (ret == -1) {
|
||||
errno = host_err;
|
||||
}
|
||||
return (ssize_t)ret;
|
||||
}
|
||||
|
||||
static ssize_t vfs_semihost_read(void* ctx, int fd, void* data, size_t size)
|
||||
{
|
||||
int host_err = 0;
|
||||
size_t ret = -1;
|
||||
|
||||
ESP_LOGV(TAG, "%s: %d %u bytes", __func__, fd, size);
|
||||
ret = generic_syscall(SYS_READ, fd, (int)data, size, 0, &host_err);
|
||||
if (ret == -1) {
|
||||
errno = host_err;
|
||||
}
|
||||
return (ssize_t)ret;
|
||||
}
|
||||
|
||||
static int vfs_semihost_close(void* ctx, int fd)
|
||||
{
|
||||
int ret = -1, host_err = 0;
|
||||
|
||||
ESP_LOGV(TAG, "%s: %d", __func__, fd);
|
||||
ret = generic_syscall(SYS_CLOSE, fd, 0, 0, 0, &host_err);
|
||||
if (ret == -1) {
|
||||
errno = host_err;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static off_t vfs_semihost_lseek(void* ctx, int fd, off_t size, int mode)
|
||||
{
|
||||
int ret = -1, host_err = 0;
|
||||
|
||||
ESP_LOGV(TAG, "%s: %d %ld %d", __func__, fd, size, mode);
|
||||
ret = generic_syscall(SYS_SEEK, fd, size, mode, 0, &host_err);
|
||||
if (ret == -1) {
|
||||
errno = host_err;
|
||||
}
|
||||
return (off_t)ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_semihost_register(const char* base_path, const char* host_path)
|
||||
{
|
||||
const esp_vfs_t vfs = {
|
||||
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
|
||||
.write_p = &vfs_semihost_write,
|
||||
.open_p = &vfs_semihost_open,
|
||||
.close_p = &vfs_semihost_close,
|
||||
.read_p = &vfs_semihost_read,
|
||||
.lseek_p = &vfs_semihost_lseek,
|
||||
};
|
||||
ESP_LOGD(TAG, "Register semihosting driver '%s' -> '%s'", base_path, host_path ? host_path : "null");
|
||||
if (!esp_cpu_in_ocd_debug_mode()) {
|
||||
ESP_LOGE(TAG, "OpenOCD is not connected!");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
int i = 0;
|
||||
for (i = 0; i < CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS; i++) {
|
||||
if (ctx_is_unused(&s_semhost_ctx[i])) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(base_path, s_semhost_ctx[i].base_path) == 0) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
if (i == CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
strlcpy(s_semhost_ctx[i].base_path, base_path, sizeof(s_semhost_ctx[i].base_path) - 1);
|
||||
if (host_path) {
|
||||
strlcpy(s_semhost_ctx[i].host_path, host_path, sizeof(s_semhost_ctx[i].host_path) - 1);
|
||||
}
|
||||
ESP_LOGD(TAG, "Register semihosting driver %d %p", i, &s_semhost_ctx[i]);
|
||||
return esp_vfs_register(base_path, &vfs, &s_semhost_ctx[i]);
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_semihost_unregister(const char* base_path)
|
||||
{
|
||||
ESP_LOGD(TAG, "Unregister semihosting driver @ '%s'", base_path);
|
||||
int i = 0;
|
||||
for (i = 0; i < CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS; i++) {
|
||||
if (s_semhost_ctx[i].base_path[0] != 0 && strcmp(base_path, s_semhost_ctx[i].base_path) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_err_t ret = esp_vfs_unregister(s_semhost_ctx[i].base_path);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
s_semhost_ctx[i].base_path[0] = 0;
|
||||
s_semhost_ctx[i].host_path[0] = 0;
|
||||
ESP_LOGD(TAG, "Unregistered semihosting driver @ '%s'", base_path);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#endif
|
@ -22,27 +22,46 @@
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp8266/uart_struct.h"
|
||||
#include "driver/uart_select.h"
|
||||
#include "driver/uart.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef portENTER_CRITICAL
|
||||
#undef portENTER_CRITICAL
|
||||
#define portENTER_CRITICAL(_lock) vPortEnterCritical()
|
||||
#include "driver/uart_select.h"
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#include "esp32/rom/uart.h"
|
||||
#include "soc/uart_periph.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2BETA
|
||||
#include "esp32s2beta/rom/uart.h"
|
||||
#include "soc/uart_periph.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP8266
|
||||
#include "rom/uart.h"
|
||||
#endif
|
||||
|
||||
#ifdef portEXIT_CRITICAL
|
||||
#undef portEXIT_CRITICAL
|
||||
#define portEXIT_CRITICAL(_lock) vPortExitCritical()
|
||||
#endif
|
||||
#define SOC_UART_NUM 2
|
||||
#define UART0 uart0
|
||||
#define UART1 uart1
|
||||
|
||||
|
||||
// TODO: make the number of UARTs chip dependent
|
||||
#define UART_NUM 2
|
||||
#define UART_NUM SOC_UART_NUM
|
||||
|
||||
// Token signifying that no character is available
|
||||
#define NONE -1
|
||||
|
||||
#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
|
||||
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CRLF
|
||||
#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR
|
||||
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CR
|
||||
#else
|
||||
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_LF
|
||||
#endif
|
||||
|
||||
#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF
|
||||
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CRLF
|
||||
#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR
|
||||
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CR
|
||||
#else
|
||||
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_LF
|
||||
#endif
|
||||
|
||||
// UART write bytes function type
|
||||
typedef void (*tx_func_t)(int, int);
|
||||
// UART read bytes function type
|
||||
@ -56,62 +75,69 @@ static int uart_rx_char(int fd);
|
||||
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};
|
||||
// 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 };
|
||||
// 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];
|
||||
typedef struct {
|
||||
// Pointers to UART peripherals
|
||||
uart_dev_t* uart;
|
||||
// One-character buffer used for newline conversion code, per UART
|
||||
int peek_char;
|
||||
// per-UART locks, lazily initialized
|
||||
_lock_t read_lock;
|
||||
_lock_t write_lock;
|
||||
// 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.
|
||||
bool non_blocking;
|
||||
// Newline conversion mode when transmitting
|
||||
esp_line_endings_t tx_mode;
|
||||
// Newline conversion mode when receiving
|
||||
esp_line_endings_t rx_mode;
|
||||
// Functions used to write bytes to UART. Default to "basic" functions.
|
||||
tx_func_t tx_func;
|
||||
// Functions used to read bytes from UART. Default to "basic" functions.
|
||||
rx_func_t rx_func;
|
||||
} vfs_uart_context_t;
|
||||
|
||||
/* Lock ensuring that uart_select is used from only one task at the time */
|
||||
static _lock_t s_one_select_lock;
|
||||
#define VFS_CTX_DEFAULT_VAL(uart_dev) (vfs_uart_context_t) {\
|
||||
.uart = (uart_dev),\
|
||||
.peek_char = NONE,\
|
||||
.tx_mode = DEFAULT_TX_MODE,\
|
||||
.rx_mode = DEFAULT_RX_MODE,\
|
||||
.tx_func = uart_tx_char,\
|
||||
.rx_func = uart_rx_char,\
|
||||
}
|
||||
|
||||
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
|
||||
//If the context should be dynamically initialized, remove this structure
|
||||
//and point s_ctx to allocated data.
|
||||
static vfs_uart_context_t s_context[UART_NUM] = {
|
||||
VFS_CTX_DEFAULT_VAL(&UART0),
|
||||
VFS_CTX_DEFAULT_VAL(&UART1),
|
||||
#if UART_NUM > 2
|
||||
VFS_CTX_DEFAULT_VAL(&UART2),
|
||||
#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
|
||||
static vfs_uart_context_t* s_ctx[UART_NUM] = {
|
||||
&s_context[0],
|
||||
&s_context[1],
|
||||
#if UART_NUM > 2
|
||||
&s_context[2],
|
||||
#endif
|
||||
};
|
||||
|
||||
// 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
|
||||
};
|
||||
typedef struct {
|
||||
esp_vfs_select_sem_t select_sem;
|
||||
fd_set *readfds;
|
||||
fd_set *writefds;
|
||||
fd_set *errorfds;
|
||||
fd_set readfds_orig;
|
||||
fd_set writefds_orig;
|
||||
fd_set errorfds_orig;
|
||||
} uart_select_args_t;
|
||||
|
||||
static uart_select_args_t **s_registered_selects = NULL;
|
||||
static int s_registered_select_num = 0;
|
||||
|
||||
static esp_err_t uart_end_select(void *end_select_args);
|
||||
|
||||
static int uart_open(const char * path, int flags, int mode)
|
||||
{
|
||||
@ -123,25 +149,29 @@ static int uart_open(const char * path, int flags, int mode)
|
||||
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);
|
||||
s_ctx[fd]->non_blocking = ((flags & O_NONBLOCK) == O_NONBLOCK);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void uart_tx_char(int fd, int c)
|
||||
{
|
||||
uart_dev_t* uart = s_uarts[fd];
|
||||
uart_dev_t* uart = s_ctx[fd]->uart;
|
||||
while (uart->status.txfifo_cnt >= 127) {
|
||||
;
|
||||
}
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
uart->fifo.rw_byte = c;
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2BETA
|
||||
uart->ahb_fifo.rw_byte = c;
|
||||
#elif CONFIG_IDF_TARGET_ESP8266
|
||||
uart->fifo.rw_byte = c;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void uart_tx_char_via_driver(int fd, int c)
|
||||
@ -152,17 +182,23 @@ static void uart_tx_char_via_driver(int fd, int c)
|
||||
|
||||
static int uart_rx_char(int fd)
|
||||
{
|
||||
uart_dev_t* uart = s_uarts[fd];
|
||||
uart_dev_t* uart = s_ctx[fd]->uart;
|
||||
if (uart->status.rxfifo_cnt == 0) {
|
||||
return NONE;
|
||||
}
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
return uart->fifo.rw_byte;
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2BETA
|
||||
return READ_PERI_REG(UART_FIFO_AHB_REG(fd));
|
||||
#elif CONFIG_IDF_TARGET_ESP8266
|
||||
return uart->fifo.rw_byte;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int uart_rx_char_via_driver(int fd)
|
||||
{
|
||||
uint8_t c;
|
||||
int timeout = s_non_blocking[fd] ? 0 : portMAX_DELAY;
|
||||
int timeout = s_ctx[fd]->non_blocking ? 0 : portMAX_DELAY;
|
||||
int n = uart_read_bytes(fd, &c, 1, timeout);
|
||||
if (n <= 0) {
|
||||
return NONE;
|
||||
@ -178,18 +214,18 @@ static ssize_t uart_write(int fd, const void * data, size_t size)
|
||||
* a dedicated UART lock if two streams (stdout and stderr) point to the
|
||||
* same UART.
|
||||
*/
|
||||
_lock_acquire_recursive(&s_uart_write_locks[fd]);
|
||||
_lock_acquire_recursive(&s_ctx[fd]->write_lock);
|
||||
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) {
|
||||
if (c == '\n' && s_ctx[fd]->tx_mode != ESP_LINE_ENDINGS_LF) {
|
||||
s_ctx[fd]->tx_func(fd, '\r');
|
||||
if (s_ctx[fd]->tx_mode == ESP_LINE_ENDINGS_CR) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
s_uart_tx_func[fd](fd, c);
|
||||
s_ctx[fd]->tx_func(fd, c);
|
||||
}
|
||||
_lock_release_recursive(&s_uart_write_locks[fd]);
|
||||
_lock_release_recursive(&s_ctx[fd]->write_lock);
|
||||
return size;
|
||||
}
|
||||
|
||||
@ -200,19 +236,19 @@ static ssize_t uart_write(int fd, const void * data, size_t size)
|
||||
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;
|
||||
if (s_ctx[fd]->peek_char != NONE) {
|
||||
int c = s_ctx[fd]->peek_char;
|
||||
s_ctx[fd]->peek_char = NONE;
|
||||
return c;
|
||||
}
|
||||
return s_uart_rx_func[fd](fd);
|
||||
return s_ctx[fd]->rx_func(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;
|
||||
assert(s_ctx[fd]->peek_char == NONE);
|
||||
s_ctx[fd]->peek_char = c;
|
||||
}
|
||||
|
||||
static ssize_t uart_read(int fd, void* data, size_t size)
|
||||
@ -220,13 +256,13 @@ 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]);
|
||||
_lock_acquire_recursive(&s_ctx[fd]->read_lock);
|
||||
while (received < size) {
|
||||
int c = uart_read_char(fd);
|
||||
if (c == '\r') {
|
||||
if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CR) {
|
||||
if (s_ctx[fd]->rx_mode == ESP_LINE_ENDINGS_CR) {
|
||||
c = '\n';
|
||||
} else if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CRLF) {
|
||||
} else if (s_ctx[fd]->rx_mode == ESP_LINE_ENDINGS_CRLF) {
|
||||
/* look ahead */
|
||||
int c2 = uart_read_char(fd);
|
||||
if (c2 == NONE) {
|
||||
@ -253,7 +289,7 @@ static ssize_t uart_read(int fd, void* data, size_t size)
|
||||
break;
|
||||
}
|
||||
}
|
||||
_lock_release_recursive(&s_uart_read_locks[fd]);
|
||||
_lock_release_recursive(&s_ctx[fd]->read_lock);
|
||||
if (received > 0) {
|
||||
return received;
|
||||
}
|
||||
@ -274,17 +310,16 @@ static int uart_close(int fd)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uart_fcntl(int fd, int cmd, va_list args)
|
||||
static int uart_fcntl(int fd, int cmd, int arg)
|
||||
{
|
||||
assert(fd >=0 && fd < 3);
|
||||
int result = 0;
|
||||
if (cmd == F_GETFL) {
|
||||
if (s_non_blocking[fd]) {
|
||||
if (s_ctx[fd]->non_blocking) {
|
||||
result |= O_NONBLOCK;
|
||||
}
|
||||
} else if (cmd == F_SETFL) {
|
||||
int arg = va_arg(args, int);
|
||||
s_non_blocking[fd] = (arg & O_NONBLOCK) != 0;
|
||||
s_ctx[fd]->non_blocking = (arg & O_NONBLOCK) != 0;
|
||||
} else {
|
||||
// unsupported operation
|
||||
result = -1;
|
||||
@ -314,135 +349,176 @@ static int uart_access(const char *path, int amode)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void select_notif_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken)
|
||||
static int uart_fsync(int fd)
|
||||
{
|
||||
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;
|
||||
}
|
||||
assert(fd >= 0 && fd < 3);
|
||||
_lock_acquire_recursive(&s_ctx[fd]->write_lock);
|
||||
uart_tx_wait_idle((uint8_t) fd);
|
||||
_lock_release_recursive(&s_ctx[fd]->write_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static esp_err_t uart_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, SemaphoreHandle_t *signal_sem)
|
||||
static esp_err_t register_select(uart_select_args_t *args)
|
||||
{
|
||||
if (_lock_try_acquire(&s_one_select_lock)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
esp_err_t ret = ESP_ERR_INVALID_ARG;
|
||||
|
||||
if (args) {
|
||||
portENTER_CRITICAL();
|
||||
const int new_size = s_registered_select_num + 1;
|
||||
if ((s_registered_selects = realloc(s_registered_selects, new_size * sizeof(uart_select_args_t *))) == NULL) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
} else {
|
||||
s_registered_selects[s_registered_select_num] = args;
|
||||
s_registered_select_num = new_size;
|
||||
ret = ESP_OK;
|
||||
}
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t unregister_select(uart_select_args_t *args)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
if (args) {
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
portENTER_CRITICAL();
|
||||
for (int i = 0; i < s_registered_select_num; ++i) {
|
||||
if (s_registered_selects[i] == args) {
|
||||
const int new_size = s_registered_select_num - 1;
|
||||
// The item is removed by overwriting it with the last item. The subsequent rellocation will drop the
|
||||
// last item.
|
||||
s_registered_selects[i] = s_registered_selects[new_size];
|
||||
s_registered_selects = realloc(s_registered_selects, new_size * sizeof(uart_select_args_t *));
|
||||
if (s_registered_selects || new_size == 0) {
|
||||
s_registered_select_num = new_size;
|
||||
ret = ESP_OK;
|
||||
} else {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void select_notif_callback_isr(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken)
|
||||
{
|
||||
portENTER_CRITICAL();
|
||||
for (int i = 0; i < s_registered_select_num; ++i) {
|
||||
uart_select_args_t *args = s_registered_selects[i];
|
||||
if (args) {
|
||||
switch (uart_select_notif) {
|
||||
case UART_SELECT_READ_NOTIF:
|
||||
if (FD_ISSET(uart_num, &args->readfds_orig)) {
|
||||
FD_SET(uart_num, args->readfds);
|
||||
esp_vfs_select_triggered_isr(args->select_sem, task_woken);
|
||||
}
|
||||
break;
|
||||
case UART_SELECT_WRITE_NOTIF:
|
||||
if (FD_ISSET(uart_num, &args->writefds_orig)) {
|
||||
FD_SET(uart_num, args->writefds);
|
||||
esp_vfs_select_triggered_isr(args->select_sem, task_woken);
|
||||
}
|
||||
break;
|
||||
case UART_SELECT_ERROR_NOTIF:
|
||||
if (FD_ISSET(uart_num, &args->errorfds_orig)) {
|
||||
FD_SET(uart_num, args->errorfds);
|
||||
esp_vfs_select_triggered_isr(args->select_sem, task_woken);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
static esp_err_t uart_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||
esp_vfs_select_sem_t select_sem, void **end_select_args)
|
||||
{
|
||||
const int max_fds = MIN(nfds, UART_NUM);
|
||||
*end_select_args = NULL;
|
||||
|
||||
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);
|
||||
if (!uart_is_driver_installed(i)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_signal_sem = signal_sem;
|
||||
uart_select_args_t *args = malloc(sizeof(uart_select_args_t));
|
||||
|
||||
_readfds = readfds;
|
||||
_writefds = writefds;
|
||||
_errorfds = exceptfds;
|
||||
|
||||
*_readfds_orig = *readfds;
|
||||
*_writefds_orig = *writefds;
|
||||
*_errorfds_orig = *exceptfds;
|
||||
if (args == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
args->select_sem = select_sem;
|
||||
args->readfds = readfds;
|
||||
args->writefds = writefds;
|
||||
args->errorfds = exceptfds;
|
||||
args->readfds_orig = *readfds; // store the original values because they will be set to zero
|
||||
args->writefds_orig = *writefds;
|
||||
args->errorfds_orig = *exceptfds;
|
||||
FD_ZERO(readfds);
|
||||
FD_ZERO(writefds);
|
||||
FD_ZERO(exceptfds);
|
||||
|
||||
portENTER_CRITICAL();
|
||||
|
||||
//uart_set_select_notif_callback sets the callbacks in UART ISR
|
||||
for (int i = 0; i < max_fds; ++i) {
|
||||
if (FD_ISSET(i, _readfds_orig)) {
|
||||
if (FD_ISSET(i, &args->readfds_orig) || FD_ISSET(i, &args->writefds_orig) || FD_ISSET(i, &args->errorfds_orig)) {
|
||||
uart_set_select_notif_callback(i, select_notif_callback_isr);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < max_fds; ++i) {
|
||||
if (FD_ISSET(i, &args->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);
|
||||
FD_SET(i, readfds);
|
||||
esp_vfs_select_triggered(args->select_sem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL(uart_get_selectlock());
|
||||
// s_one_select_lock is not released on successfull exit - will be
|
||||
// released in uart_end_select()
|
||||
esp_err_t ret = register_select(args);
|
||||
if (ret != ESP_OK) {
|
||||
portEXIT_CRITICAL();
|
||||
free(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL();
|
||||
|
||||
*end_select_args = args;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void uart_end_select()
|
||||
static esp_err_t uart_end_select(void *end_select_args)
|
||||
{
|
||||
portENTER_CRITICAL(uart_get_selectlock());
|
||||
uart_select_args_t *args = end_select_args;
|
||||
|
||||
portENTER_CRITICAL();
|
||||
esp_err_t ret = unregister_select(args);
|
||||
for (int i = 0; i < UART_NUM; ++i) {
|
||||
uart_set_select_notif_callback(i, NULL);
|
||||
}
|
||||
portEXIT_CRITICAL();
|
||||
|
||||
_signal_sem = NULL;
|
||||
|
||||
_readfds = NULL;
|
||||
_writefds = NULL;
|
||||
_errorfds = NULL;
|
||||
|
||||
if (_readfds_orig) {
|
||||
free(_readfds_orig);
|
||||
_readfds_orig = NULL;
|
||||
if (args) {
|
||||
free(args);
|
||||
}
|
||||
|
||||
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);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SUPPORT_TERMIOS
|
||||
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
|
||||
static int uart_tcsetattr(int fd, int optional_actions, const struct termios *p)
|
||||
{
|
||||
if (fd < 0 || fd >= UART_NUM) {
|
||||
@ -479,11 +555,11 @@ static int uart_tcsetattr(int fd, int optional_actions, const struct termios *p)
|
||||
}
|
||||
|
||||
if (p->c_iflag & IGNCR) {
|
||||
s_rx_mode[fd] = ESP_LINE_ENDINGS_CRLF;
|
||||
s_ctx[fd]->rx_mode = ESP_LINE_ENDINGS_CRLF;
|
||||
} else if (p->c_iflag & ICRNL) {
|
||||
s_rx_mode[fd] = ESP_LINE_ENDINGS_CR;
|
||||
s_ctx[fd]->rx_mode = ESP_LINE_ENDINGS_CR;
|
||||
} else {
|
||||
s_rx_mode[fd] = ESP_LINE_ENDINGS_LF;
|
||||
s_ctx[fd]->rx_mode = ESP_LINE_ENDINGS_LF;
|
||||
}
|
||||
|
||||
// output line endings are not supported because there is no alternative in termios for converting LF to CR
|
||||
@ -662,9 +738,9 @@ static int uart_tcgetattr(int fd, struct termios *p)
|
||||
|
||||
memset(p, 0, sizeof(struct termios));
|
||||
|
||||
if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CRLF) {
|
||||
if (s_ctx[fd]->rx_mode == ESP_LINE_ENDINGS_CRLF) {
|
||||
p->c_iflag |= IGNCR;
|
||||
} else if (s_rx_mode[fd] == ESP_LINE_ENDINGS_CR) {
|
||||
} else if (s_ctx[fd]->rx_mode == ESP_LINE_ENDINGS_CR) {
|
||||
p->c_iflag |= ICRNL;
|
||||
}
|
||||
|
||||
@ -892,9 +968,9 @@ static int uart_tcflush(int fd, int select)
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif // CONFIG_SUPPORT_TERMIOS
|
||||
#endif // CONFIG_VFS_SUPPORT_TERMIOS
|
||||
|
||||
void esp_vfs_dev_uart_register()
|
||||
void esp_vfs_dev_uart_register(void)
|
||||
{
|
||||
esp_vfs_t vfs = {
|
||||
.flags = ESP_VFS_FLAG_DEFAULT,
|
||||
@ -904,15 +980,16 @@ void esp_vfs_dev_uart_register()
|
||||
.close = &uart_close,
|
||||
.read = &uart_read,
|
||||
.fcntl = &uart_fcntl,
|
||||
.fsync = &uart_fsync,
|
||||
.access = &uart_access,
|
||||
.start_select = &uart_start_select,
|
||||
.end_select = &uart_end_select,
|
||||
#ifdef CONFIG_SUPPORT_TERMIOS
|
||||
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
|
||||
.tcsetattr = &uart_tcsetattr,
|
||||
.tcgetattr = &uart_tcgetattr,
|
||||
.tcdrain = &uart_tcdrain,
|
||||
.tcflush = &uart_tcflush,
|
||||
#endif // CONFIG_SUPPORT_TERMIOS
|
||||
#endif // CONFIG_VFS_SUPPORT_TERMIOS
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_vfs_register("/dev/uart", &vfs, NULL));
|
||||
}
|
||||
@ -920,31 +997,33 @@ void esp_vfs_dev_uart_register()
|
||||
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;
|
||||
s_ctx[i]->rx_mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
void esp_vfs_dev_uart_set_tx_line_endings(esp_line_endings_t mode)
|
||||
{
|
||||
s_tx_mode = mode;
|
||||
for (int i = 0; i < UART_NUM; ++i) {
|
||||
s_ctx[i]->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]);
|
||||
_lock_acquire_recursive(&s_ctx[uart_num]->read_lock);
|
||||
_lock_acquire_recursive(&s_ctx[uart_num]->write_lock);
|
||||
s_ctx[uart_num]->tx_func = uart_tx_char;
|
||||
s_ctx[uart_num]->rx_func = uart_rx_char;
|
||||
_lock_release_recursive(&s_ctx[uart_num]->write_lock);
|
||||
_lock_release_recursive(&s_ctx[uart_num]->read_lock);
|
||||
}
|
||||
|
||||
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]);
|
||||
_lock_acquire_recursive(&s_ctx[uart_num]->read_lock);
|
||||
_lock_acquire_recursive(&s_ctx[uart_num]->write_lock);
|
||||
s_ctx[uart_num]->tx_func = uart_tx_char_via_driver;
|
||||
s_ctx[uart_num]->rx_func = uart_rx_char_via_driver;
|
||||
_lock_release_recursive(&s_ctx[uart_num]->write_lock);
|
||||
_lock_release_recursive(&s_ctx[uart_num]->read_lock);
|
||||
}
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include "driver/uart.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_USING_ESP_VFS
|
||||
esp_err_t example_configure_stdin_stdout(void)
|
||||
{
|
||||
// Initialize VFS & UART so we can use std::cout/cin
|
||||
@ -29,4 +28,3 @@ esp_err_t example_configure_stdin_stdout(void)
|
||||
esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
@ -1,4 +1,3 @@
|
||||
CONFIG_USING_ESP_VFS=y
|
||||
CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y
|
||||
CONFIG_SUPPORT_TERMIOS=y
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
CONFIG_USING_ESP_VFS=y
|
||||
CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y
|
||||
CONFIG_SUPPORT_TERMIOS=y
|
||||
|
||||
|
@ -1,8 +1,3 @@
|
||||
#
|
||||
# Virtual file system
|
||||
#
|
||||
CONFIG_USING_ESP_VFS=y
|
||||
|
||||
#
|
||||
# PThreads
|
||||
#
|
||||
|
Reference in New Issue
Block a user