diff --git a/cpukit/dev/io/iodev.c b/cpukit/dev/io/iodev.c new file mode 100644 index 0000000000..63376321a4 --- /dev/null +++ b/cpukit/dev/io/iodev.c @@ -0,0 +1,353 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/* + * Copyright (C) 2026 Aaron Nyholm + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int rtems_iodev_do_init( + rtems_iodev *iodev, + void ( *destroy )( rtems_iodev *iodev ) +); + +static int rtems_iodev_ioctl( + rtems_libio_t *iop, + ioctl_command_t command, + void *arg +); + +static int rtems_iodev_mmap( + rtems_libio_t *iop, + void **addr, + size_t len, + int prot, + off_t off +); + +static int rtems_iodev_get_region( struct rtems_iodev *iodev, void *arg ); + +static int rtems_iodev_get_event_info( struct rtems_iodev *iodev, void *arg ); + +static int rtems_iodev_event_wait_on( struct rtems_iodev *iodev, void *arg ); + +static void rtems_iodev_node_destroy( IMFS_jnode_t *node ); + +static const rtems_filesystem_file_handlers_r rtems_iodev_handler = { + .open_h = rtems_filesystem_default_open, + .close_h = rtems_filesystem_default_close, + .read_h = rtems_filesystem_default_read, + .write_h = rtems_filesystem_default_write, + .ioctl_h = rtems_iodev_ioctl, + .lseek_h = rtems_filesystem_default_lseek, + .fstat_h = IMFS_stat, + .ftruncate_h = rtems_filesystem_default_ftruncate, + .fsync_h = rtems_filesystem_default_fsync_or_fdatasync, + .fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync, + .fcntl_h = rtems_filesystem_default_fcntl, + .kqfilter_h = rtems_filesystem_default_kqfilter, + .mmap_h = rtems_iodev_mmap, + .poll_h = rtems_filesystem_default_poll, + .readv_h = rtems_filesystem_default_readv, + .writev_h = rtems_filesystem_default_writev +}; + +static const IMFS_node_control + rtems_iodev_node_control = IMFS_GENERIC_INITIALIZER( + &rtems_iodev_handler, + IMFS_node_initialize_generic, + rtems_iodev_node_destroy + ); + +static void rtems_iodev_node_destroy( IMFS_jnode_t *node ) +{ + rtems_iodev *iodev; + + iodev = IMFS_generic_get_context_by_node( node ); + + ( *iodev->destroy )( iodev ); + + IMFS_node_destroy_default( node ); +} + +static void rtems_iodev_obtain( rtems_iodev *iodev ) +{ + rtems_recursive_mutex_lock( &iodev->mutex ); +} + +static void rtems_iodev_release( rtems_iodev *iodev ) +{ + rtems_recursive_mutex_unlock( &iodev->mutex ); +} + +static int rtems_iodev_ioctl( + rtems_libio_t *iop, + ioctl_command_t command, + void *arg +) +{ + rtems_iodev *iodev = IMFS_generic_get_context_by_iop( iop ); + int err = 0; + + rtems_iodev_obtain( iodev ); + + switch ( command ) { + case RTEMS_IODEV_IOCTL_OBTAIN: + rtems_iodev_obtain( iodev ); + err = 0; + break; + case RTEMS_IODEV_IOCTL_RELEASE: + rtems_iodev_release( iodev ); + err = 0; + break; + case RTEMS_IODEV_IOCTL_DEVICE_INFO: + err = iodev->get_device_info( iodev, arg ); + break; + case RTEMS_IODEV_IOCTL_REGION_COUNT: + err = iodev->get_region_count( iodev, arg ); + break; + case RTEMS_IODEV_IOCTL_REGION_GET: + err = rtems_iodev_get_region( iodev, arg ); + break; + case RTEMS_IODEV_IOCTL_EVENT_COUNT: + err = iodev->get_event_count( iodev, arg ); + break; + case RTEMS_IODEV_IOCTL_EVENT_INFO: + err = rtems_iodev_get_event_info( iodev, arg ); + break; + case RTEMS_IODEV_IOCTL_EVENT_WAIT: + err = rtems_iodev_event_wait_on( iodev, arg ); + break; + default: + err = EINVAL; + } + + rtems_iodev_release( iodev ); + if ( err != 0 ) { + rtems_set_errno_and_return_minus_one( err ); + } else { + return 0; + } +} + +static int rtems_iodev_mmap( + rtems_libio_t *iop, + void **addr, + size_t len, + int prot, + off_t off +) +{ + rtems_iodev *iodev = IMFS_generic_get_context_by_iop( iop ); + rtems_iodev_region *regions; + size_t region_count = 0; + + if ( len == 0 || *addr != NULL || off < 0 ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + if ( prot != ( PROT_READ | PROT_WRITE ) ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + iodev->get_region_count( iodev, ®ion_count ); + if ( (size_t) off > region_count ) { + rtems_set_errno_and_return_minus_one( EOVERFLOW ); + } + + iodev->get_regions( iodev, ®ions ); + if ( len > regions[ off ].size ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + *addr = regions[ off ].address; + + return 0; +} + +static int rtems_iodev_get_region( struct rtems_iodev *iodev, void *arg ) +{ + rtems_iodev_region *regions; + rtems_iodev_region *region; + size_t region_count = 0; + + region = (rtems_iodev_region *) arg; + + iodev->get_region_count( iodev, ®ion_count ); + if ( region->index >= region_count ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + iodev->get_regions( iodev, ®ions ); + region->address = regions[ region->index ].address; + region->size = regions[ region->index ].size; + region->name = regions[ region->index ].name; + + return 0; +} + +static int rtems_iodev_get_event_info( struct rtems_iodev *iodev, void *arg ) +{ + rtems_iodev_event_info *e_info; + int status; + size_t event_count; + + e_info = (rtems_iodev_event_info *) arg; + + iodev->get_event_count( iodev, &event_count ); + if ( e_info->index >= event_count ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + if ( iodev->get_event_info == NULL ) { + rtems_set_errno_and_return_minus_one( ENODEV ); + } + + status = ( *iodev->get_event_info )( iodev, e_info ); + + return status; +} + +static int rtems_iodev_event_wait_on( struct rtems_iodev *iodev, void *arg ) +{ + rtems_iodev_event_args *e_args; + int status; + size_t event_count; + + e_args = (rtems_iodev_event_args *) arg; + + iodev->get_event_count( iodev, &event_count ); + if ( e_args->index >= event_count ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + if ( iodev->event_wait == NULL ) { + rtems_set_errno_and_return_minus_one( ENODEV ); + } + + status = ( *iodev->event_wait )( iodev, e_args ); + + return status; +} + +int rtems_iodev_register( rtems_iodev *iodev, const char *iodev_path ) +{ + int rv; + + rv = IMFS_make_generic_node( + iodev_path, + S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, + &rtems_iodev_node_control, + iodev + ); + + return rv; +} + +int rtems_iodev_unregister_and_destroy( const char *iodev_path ) +{ + return unlink( iodev_path ); +} + +static int rtems_iodev_do_init( + rtems_iodev *iodev, + void ( *destroy )( rtems_iodev *iodev ) +) +{ + rtems_recursive_mutex_init( &iodev->mutex, "RTEMS_IODEV device" ); + iodev->destroy = destroy; + /* other fields are guaranteed nulled by caller */ + + return 0; +} + +void rtems_iodev_destroy_unregistered( rtems_iodev *iodev ) +{ + ( *iodev->destroy )( iodev ); +} + +static void iodev_destroy_internal( rtems_iodev *iodev ) +{ + if ( iodev->priv_destroy != NULL ) { + ( *iodev->priv_destroy )( iodev ); + } + + rtems_recursive_mutex_destroy( &iodev->mutex ); +} + +static void iodev_destroy_and_free_internal( rtems_iodev *iodev ) +{ + if ( iodev == NULL ) { + return; + } + iodev_destroy_internal( iodev ); + free( iodev ); + iodev = NULL; +} + +int rtems_iodev_init( rtems_iodev *iodev ) +{ + memset( iodev, 0, sizeof( *iodev ) ); + + return rtems_iodev_do_init( iodev, iodev_destroy_internal ); +} + +rtems_iodev *rtems_iodev_alloc_and_init( size_t size ) +{ + rtems_iodev *iodev = NULL; + + if ( size >= sizeof( *iodev ) ) { + iodev = calloc( 1, size ); + if ( iodev != NULL ) { + int rv; + + rv = rtems_iodev_do_init( iodev, iodev_destroy_and_free_internal ); + if ( rv != 0 ) { + rtems_recursive_mutex_destroy( &iodev->mutex ); + free( iodev ); + return NULL; + } + } + } + + return iodev; +} diff --git a/cpukit/include/dev/io/iodev.h b/cpukit/include/dev/io/iodev.h new file mode 100644 index 0000000000..a0a5848090 --- /dev/null +++ b/cpukit/include/dev/io/iodev.h @@ -0,0 +1,360 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSGenericIODevAPI + * + * @brief Generic IO Device API + */ + +/* + * Copyright (C) 2026 Aaron Nyholm + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _DEV_IODEV_H +#define _DEV_IODEV_H + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct rtems_iodev rtems_iodev; + +/** + * @defgroup RTEMSGenericIOSpaceAPI Generic IO Space API + * + * @ingroup RTEMSDeviceDrivers + * + * @brief Generic IO Space API to wrap IO memory regions. + * + * @{ + */ + +/* IOCTL Calls */ + +/** + * @brief Obtains the iodev device. + * + * This command has no argument. + */ +#define RTEMS_IODEV_IOCTL_OBTAIN 0 +/** + * @brief Releases the iodev device. + * + * This command has no argument. + */ +#define RTEMS_IODEV_IOCTL_RELEASE 1 + +/** + * @brief Get the device information, defined by the driver. + * + * @param[out] info pointer to struct containing device information. + */ +#define RTEMS_IODEV_IOCTL_DEVICE_INFO 2 + +/** + * @brief Get number of events in iodev. + * + * @param[out] count size_t containing the number of regions. + */ +#define RTEMS_IODEV_IOCTL_REGION_COUNT 3 + +/** + * @brief Get number of events in iodev. + * + * @param[in,out] arg Pointer to rtems_iodev_region struct + * with offset and length for return values. + */ +#define RTEMS_IODEV_IOCTL_REGION_GET 4 + +/** + * @brief Get number of events in iodev. + * + * @param[out] count size_t containing the number of events. + */ +#define RTEMS_IODEV_IOCTL_EVENT_COUNT 5 + +/** + * @brief Get information for given event ID. + * + * @param[in,out] event_info Pointer to rtems_iodev_event_info struct + * containing event index and returned information. + */ +#define RTEMS_IODEV_IOCTL_EVENT_INFO 6 + +/** + * @brief Wait or poll on event. Set timeout to zero for indefinite wait. + * + * @param[in,out] event_args Pointer to rtems_iodev_event_args struct + * containing event index, timeout length and iodev driver return value. + */ +#define RTEMS_IODEV_IOCTL_EVENT_WAIT 7 + +/** + * @brief The maximum number of IO regions. + */ +#define RTEMS_IODEV_MAX_REGIONS 32 + +/** + * @brief General definition for an iodev region. + */ +typedef struct rtems_iodev_region { + /** + * @brief Region index. + */ + size_t index; + /** + * @brief Region address. + */ + void *address; + /** + * @brief Length of region. + */ + size_t size; + /** + * @brief Name of region. + */ + const char *name; +} rtems_iodev_region; + +/** + * @brief General description of iodev event. + */ +typedef struct rtems_iodev_event_info { + /** + * @brief Event index. + */ + uint32_t index; + /** + * @brief Name of event. + */ + const char *name; + /** + * @brief Additional argument; + */ + void *args; +} rtems_iodev_event_info; + +/** + * @brief General definition of iodev event arguments. + */ +typedef struct rtems_iodev_event_args { + /** + * @brief Event index. + */ + uint32_t index; + /** + * @brief Timeout length. + */ + struct timespec timeout; + /** + * @brief Returns if event timed out. + */ + bool timedout; + /** + * @brief Additional argument. + */ + void *args; +} rtems_iodev_event_args; + +/** + * @brief iodev device. + */ +struct rtems_iodev { + /** + * @brief Call to the device driver to get device information. + * + * @param[in] iodev Pointer to iodev device. + * @param[out] info Pointer to device information. + * + * @retval 0 Successful operation. + * @retval non-zero Failed operation. + */ + int ( *get_device_info )( rtems_iodev *iodev, void *info ); + /** + * + * @brief Call to the device driver to get number of events. + * + * @param[in] iodev Pointer to iodev device. + * @param[in,out] event_count Pointer to number of events. + * + * @retval 0 Successful operation. + * @retval non-zero Failed operation. + */ + int ( *get_event_count )( rtems_iodev *iodev, size_t *event_count ); + + /** + * @brief Call to the device driver to get info on event. + * + * @param[in] iodev Pointer to iodev device. + * @param[in,out] event_args Arguments for wait operation. + * + * @retval 0 Successful operation. + * @retval non-zero Failed operation. + */ + int ( *get_event_info )( + rtems_iodev *iodev, + rtems_iodev_event_info *event_info + ); + + /** + * @brief Call to the device driver to wait on event. + * + * @param[in] iodev Pointer to iodev device. + * @param[in,out] event_args Arguments for wait operation. + * + * @retval 0 Successful operation. + * @retval non-zero Failed operation. + */ + int ( *event_wait )( + rtems_iodev *iodev, + rtems_iodev_event_args *event_args + ); + + /** + * @brief Call to the device driver to get array of regions. + * + * @param[in] iodev Pointer to iodev device. + * @param[in,out] regions Pointer to array of regions. + * + * @retval 0 Successful operation. + * @retval non-zero Failed operation. + */ + int ( *get_regions )( rtems_iodev *iodev, rtems_iodev_region **regions ); + + /** + * @brief Call to the device driver to get number of regions. + * + * @param[in] iodev Pointer to iodev device. + * @param[in,out] region_count Pointer to number of regions. + * + * @retval 0 Successful operation. + * @retval non-zero Failed operation. + */ + int ( *get_region_count )( rtems_iodev *iodev, size_t *region_count ); + + /** + * @brief Destroys the iodev device. + * + * @param[in] iodev Pointer to iodev device. + */ + void ( *destroy )( rtems_iodev *iodev ); + + /** + * @brief Callback to destroy private data not owned directly by the iodev + * framework. + * + * @param[in] iodev Pointer to iodev device. + */ + void ( *priv_destroy )( rtems_iodev *iodev ); + + /** + * @brief Pointer to device driver. + */ + void *driver; + + /** + * @brief Mutex to protect the iodev device access. + */ + rtems_recursive_mutex mutex; +}; + +/** + * @brief Allocate and initialize the iodev device. + * + * After a successful allocation and initialization of the iodev device + * it must be destroyed via rtems_iodev_destroy_unregistered(). + * + * @param[in] size The number of bytes to allocate. + * + * @retval NULL Failed to set up iodev device. + * @retval non-NULL The new iodev device. + */ +rtems_iodev *rtems_iodev_alloc_and_init( size_t size ); + +/** + * @brief Initialize the iodev device. + * + * After a successful initialization of the iodev device it must be + * destroyed via rtems_iodev_destroy_unregistered(). + * + * @param[in] iodev The iodev device to initialize. + * + * @retval non-zero Failed to set up iodev device. + * @retval 0 Successful set up of iodev device. + */ +int rtems_iodev_init( rtems_iodev *iodev ); + +/** + * @brief Register the iodev device. + * + * This function always claims ownership of the iodev device passed to it. Once + * successfully registered, the iodev device can only be destroyed by + * unregistering it. + * + * After initialization and before registration rtems_iodev device driver + * functions need to be set. + * + * @param[in] iodev The iodev device. + * @param[in] iodev_path The path to the iodev device file. + * + * @retval 0 Successful operation. + * @retval non-zero Failed operation. + */ +int rtems_iodev_register( rtems_iodev *iodev, const char *iodev_path ); + +/** + * @brief Unregister and destroy the iodev device. + * + * @param[in] iodev The iodev device. + * + * @retval 0 Successful operation. + * @retval non-zero Failed operation. + */ +int rtems_iodev_unregister_and_destroy( const char *iodev_path ); + +/** + * @brief Destroys the iodev device. Frees the iodev if initialised with + * rtems_iodev_alloc_and_init. + * + * This function must only be used on rtems_iodev instances that have not been + * successfully registered with rtems_iodev_register. + * + * @param[in] iodev The iodev device. + */ +void rtems_iodev_destroy_unregistered( rtems_iodev *iodev ); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* _DEV_IODEV_H */ diff --git a/spec/build/cpukit/librtemscpu.yml b/spec/build/cpukit/librtemscpu.yml index 681bd5e706..68d9aa1640 100644 --- a/spec/build/cpukit/librtemscpu.yml +++ b/spec/build/cpukit/librtemscpu.yml @@ -71,6 +71,9 @@ install: - destination: ${BSP_INCLUDEDIR}/dev/flash source: - cpukit/include/dev/flash/flashdev.h +- destination: ${BSP_INCLUDEDIR}/dev/io + source: + - cpukit/include/dev/io/iodev.h - destination: ${BSP_INCLUDEDIR}/linux source: - cpukit/include/linux/i2c-dev.h @@ -571,6 +574,7 @@ source: - cpukit/dev/i2c/xilinx-axi-i2c.c - cpukit/dev/ioprintf.c - cpukit/dev/iorelax.c +- cpukit/dev/io/iodev.c - cpukit/dev/serial/sc16is752-spi.c - cpukit/dev/serial/sc16is752.c - cpukit/dev/spi/spi-bus.c diff --git a/spec/build/testsuites/libtests/grp.yml b/spec/build/testsuites/libtests/grp.yml index 1993e6dc4d..44cbbd985f 100644 --- a/spec/build/testsuites/libtests/grp.yml +++ b/spec/build/testsuites/libtests/grp.yml @@ -174,6 +174,8 @@ links: uid: iconvclose - role: build-dependency uid: iconvopen +- role: build-dependency + uid: iodev01 - role: build-dependency uid: irqs01 - role: build-dependency diff --git a/spec/build/testsuites/libtests/iodev01.yml b/spec/build/testsuites/libtests/iodev01.yml new file mode 100644 index 0000000000..4fe495f9f8 --- /dev/null +++ b/spec/build/testsuites/libtests/iodev01.yml @@ -0,0 +1,20 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: + - Copyright (C) 2026 Aaron Nyholm +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: [] +ldflags: [] +links: [] +source: + - testsuites/libtests/iodev01/init.c + - testsuites/libtests/iodev01/test_iodev.c +stlib: [] +target: testsuites/libtests/iodev01.exe +type: build +use-after: [] +use-before: [] diff --git a/testsuites/libtests/iodev01/init.c b/testsuites/libtests/iodev01/init.c new file mode 100644 index 0000000000..10030a630c --- /dev/null +++ b/testsuites/libtests/iodev01/init.c @@ -0,0 +1,305 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (C) 2026 Aaron Nyholm + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tmacros.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "test_iodev.h" + +#define TEST_NAME_LENGTH 10 + +#define IODEV_PATH "/dev/iodev0" + +const char rtems_test_name[] = "IODEV 1"; + +static void run_test( void ); + +static void assert_timing( struct timespec *start_time, int expected_secs ) +{ + struct timespec end_time; + struct timespec duration; + + clock_gettime( CLOCK_REALTIME, &end_time ); + _Timespec_Subtract( start_time, &end_time, &duration ); + rtems_test_assert( duration.tv_sec >= expected_secs - 1 ); + rtems_test_assert( duration.tv_sec <= expected_secs ); +} + +static void run_test( void ) +{ + rtems_iodev *iodev; + rtems_resource_snapshot snapshot; + int status; + FILE *file; + int fd; + size_t count; + rtems_iodev_region region; + rtems_iodev_event_info event; + rtems_iodev_event_args event_args; + size_t i; + void *map_addr; + struct timespec start_time; + struct test_iodev_dev_info test_dev_info; + + /* Check resource usage on creation and deletion */ + rtems_resource_snapshot_take( &snapshot ); + + iodev = test_iodev_init(); + rtems_test_assert( iodev != NULL ); + + rtems_iodev_destroy_unregistered( iodev ); + iodev = NULL; + + rtems_test_assert( rtems_resource_snapshot_check( &snapshot ) ); + + /* Check resource usage on registration */ + rtems_resource_snapshot_take( &snapshot ); + + iodev = test_iodev_init(); + rtems_test_assert( iodev != NULL ); + + status = rtems_iodev_register( iodev, IODEV_PATH ); + rtems_test_assert( !status ); + + status = rtems_iodev_unregister_and_destroy( IODEV_PATH ); + rtems_test_assert( !status ); + + rtems_test_assert( rtems_resource_snapshot_check( &snapshot ) ); + + /* Initialize the iodev device driver and iodev */ + iodev = test_iodev_init(); + rtems_test_assert( iodev != NULL ); + + /* Register the iodev as a device */ + status = rtems_iodev_register( iodev, IODEV_PATH ); + rtems_test_assert( !status ); + + file = fopen( IODEV_PATH, "r+" ); + rtems_test_assert( file != NULL ); + + fd = fileno( file ); + + status = ioctl( fd, RTEMS_IODEV_IOCTL_DEVICE_INFO, &test_dev_info ); + rtems_test_assert( !status ); + rtems_test_assert( test_dev_info.id == TEST_IODEV_TEST_DEV_ID ); + + /* Test valid regions */ + status = ioctl( fd, RTEMS_IODEV_IOCTL_REGION_COUNT, &count ); + rtems_test_assert( !status ); + rtems_test_assert( count == TEST_IODEV_REGION_COUNT ); + + for ( i = 0; i < count; i++ ) { + region.index = i; + region.address = NULL; + region.size = 0; + region.name = NULL; + status = ioctl( fd, RTEMS_IODEV_IOCTL_REGION_GET, ®ion ); + rtems_test_assert( !status ); + rtems_test_assert( region.size == TEST_IODEV_REGION_SIZE ); + rtems_test_assert( region.address != NULL ); + rtems_test_assert( region.name != NULL ); + + map_addr = mmap( + NULL, + region.size, + ( PROT_READ | PROT_WRITE ), + MAP_SHARED, + fd, + region.index + ); + rtems_test_assert( map_addr != MAP_FAILED ); + rtems_test_assert( map_addr == region.address ); + + status = munmap( map_addr, region.size ); + rtems_test_assert( !status ); + } + + /* Test invalid region failure */ + map_addr = mmap( + NULL, + region.size, + ( PROT_READ | PROT_WRITE ), + MAP_SHARED, + fd, + TEST_IODEV_REGION_COUNT + ); + rtems_test_assert( map_addr == MAP_FAILED ); + + /* Test event info */ + status = ioctl( fd, RTEMS_IODEV_IOCTL_EVENT_COUNT, &count ); + rtems_test_assert( !status ); + rtems_test_assert( count == TEST_IODEV_EVENT_COUNT ); + + for ( i = 0; i < count; i++ ) { + event.index = i; + status = ioctl( fd, RTEMS_IODEV_IOCTL_EVENT_INFO, &event ); + rtems_test_assert( !status ); + rtems_test_assert( event.name != NULL ); + rtems_test_assert( event.index == i ); + } + + /* Test TEST_IODEV_EVENT_SECS second event */ + event_args.index = 0; + event_args.timeout.tv_sec = 0; + event_args.timeout.tv_nsec = 0; + event_args.args = NULL; + + clock_gettime( CLOCK_REALTIME, &start_time ); + + status = ioctl( fd, RTEMS_IODEV_IOCTL_EVENT_WAIT, &event_args ); + + rtems_test_assert( !status ); + assert_timing( &start_time, TEST_IODEV_EVENT_SECS ); + rtems_test_assert( event_args.timedout == false ); + + /* Test large timeout */ + event_args.index = 0; + event_args.timeout.tv_sec = 8; + event_args.timeout.tv_nsec = 0; + event_args.args = NULL; + + clock_gettime( CLOCK_REALTIME, &start_time ); + + status = ioctl( fd, RTEMS_IODEV_IOCTL_EVENT_WAIT, &event_args ); + + rtems_test_assert( !status ); + assert_timing( &start_time, TEST_IODEV_EVENT_SECS ); + rtems_test_assert( event_args.timedout == false ); + + event_args.index = 1; + event_args.timeout.tv_sec = 8; + event_args.timeout.tv_nsec = 0; + event_args.args = NULL; + + clock_gettime( CLOCK_REALTIME, &start_time ); + + status = ioctl( fd, RTEMS_IODEV_IOCTL_EVENT_WAIT, &event_args ); + + rtems_test_assert( !status ); + assert_timing( &start_time, 8 ); + rtems_test_assert( event_args.timedout == true ); + + /* Test timeout feature */ + event_args.index = 0; + event_args.timeout.tv_sec = 2; + event_args.timeout.tv_nsec = 0; + event_args.args = NULL; + + clock_gettime( CLOCK_REALTIME, &start_time ); + + status = ioctl( fd, RTEMS_IODEV_IOCTL_EVENT_WAIT, &event_args ); + + rtems_test_assert( !status ); + assert_timing( &start_time, 2 ); + rtems_test_assert( event_args.timedout == true ); + + /* Test invalid event */ + event_args.index = TEST_IODEV_EVENT_COUNT; + event_args.timeout.tv_sec = 2; + event_args.timeout.tv_nsec = 0; + event_args.args = NULL; + + status = ioctl( fd, RTEMS_IODEV_IOCTL_EVENT_WAIT, &event_args ); + rtems_test_assert( status ); + + /* Test MAP_PRIVATE failure */ + map_addr = + mmap( NULL, region.size, ( PROT_READ | PROT_WRITE ), MAP_PRIVATE, fd, 0 ); + rtems_test_assert( map_addr == MAP_FAILED ); + + /* Test MAP_FIXED failure */ + map_addr = + mmap( NULL, region.size, ( PROT_READ | PROT_WRITE ), MAP_FIXED, fd, 0 ); + rtems_test_assert( map_addr == MAP_FAILED ); + + /* Test PROT_READ and PROT_WRITE requirement */ + map_addr = mmap( NULL, region.size, ( PROT_READ ), MAP_SHARED, fd, 0 ); + rtems_test_assert( map_addr == MAP_FAILED ); + + map_addr = mmap( NULL, region.size, ( PROT_WRITE ), MAP_SHARED, fd, 0 ); + rtems_test_assert( map_addr == MAP_FAILED ); + + map_addr = mmap( NULL, region.size, ( PROT_NONE ), MAP_SHARED, fd, 0 ); + rtems_test_assert( map_addr == MAP_FAILED ); + + map_addr = mmap( NULL, region.size, ( PROT_EXEC ), MAP_SHARED, fd, 0 ); + rtems_test_assert( map_addr == MAP_FAILED ); + + map_addr = mmap( + NULL, + region.size, + ( PROT_READ | PROT_WRITE | PROT_EXEC ), + MAP_SHARED, + fd, + 0 + ); + rtems_test_assert( map_addr == MAP_FAILED ); + + status = fclose( file ); + rtems_test_assert( !status ); + + status = rtems_iodev_unregister_and_destroy( IODEV_PATH ); + rtems_test_assert( !status ); +} + +static void Init( rtems_task_argument arg ) +{ + (void) arg; + + TEST_BEGIN(); + + run_test(); + + TEST_END(); + rtems_test_exit( 0 ); +} + +#define CONFIGURE_MICROSECONDS_PER_TICK 2000 + +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER + +#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 7 + +#define CONFIGURE_MAXIMUM_TASKS 2 + +#define CONFIGURE_MAXIMUM_SEMAPHORES 1 + +#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION + +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE + +#define CONFIGURE_INIT + +#include diff --git a/testsuites/libtests/iodev01/iodev01.doc b/testsuites/libtests/iodev01/iodev01.doc new file mode 100644 index 0000000000..0c710ccdab --- /dev/null +++ b/testsuites/libtests/iodev01/iodev01.doc @@ -0,0 +1,15 @@ +This file describes the directives and concepts tested by this test set. + +test set name: iodev01 + +directives: + + - rtems_iodev_alloc_and_init + - rtems_iodev_init + - rtems_iodev_register + - rtems_iodev_unregister_and_destroy + - rtems_iodev_destroy_unregistered + +concepts: + + - Ensure that the iodev driver API works. diff --git a/testsuites/libtests/iodev01/iodev01.scn b/testsuites/libtests/iodev01/iodev01.scn new file mode 100644 index 0000000000..1adbcc38b1 --- /dev/null +++ b/testsuites/libtests/iodev01/iodev01.scn @@ -0,0 +1,2 @@ +*** BEGIN OF TEST IODEV 1 *** +*** END OF TEST IODEV 1 *** diff --git a/testsuites/libtests/iodev01/test_iodev.c b/testsuites/libtests/iodev01/test_iodev.c new file mode 100644 index 0000000000..88841f9c29 --- /dev/null +++ b/testsuites/libtests/iodev01/test_iodev.c @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/* + * Copyright (C) 2026 Aaron Nyholm + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include + +#include "test_iodev.h" + +#define TIMED_RETURN_SEC TEST_IODEV_EVENT_SECS + +typedef struct test_iodev { + rtems_iodev *iodev; + rtems_iodev_region regions[ TEST_IODEV_REGION_COUNT ]; + rtems_id server_id; + rtems_id client_id; + rtems_mutex lock; + bool waiting; +} test_iodev; + +static test_iodev tiodev; + +int test_iodev_get_device_info( rtems_iodev *iodev, void *event_info ); + +int test_iodev_get_event_info( + rtems_iodev *iodev, + struct rtems_iodev_event_info *event_info +); + +int test_iodev_event_wait( + rtems_iodev *iodev, + struct rtems_iodev_event_args *event_args +); + +int test_iodev_get_regions( rtems_iodev *iodev, rtems_iodev_region **regions ); + +int test_iodev_get_region_count( rtems_iodev *iodev, size_t *region_count ); + +int test_iodev_get_event_count( rtems_iodev *iodev, size_t *event_count ); + +void test_iodev_priv_destroy( rtems_iodev *iodev ); + +static void test_iodev_server() +{ + rtems_status_code sc; + rtems_event_set event_in; + + for ( ;; ) { + sc = rtems_event_receive( + RTEMS_EVENT_0, + RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &event_in + ); + if ( sc != RTEMS_SUCCESSFUL ) { + continue; + } + + rtems_task_wake_after( + RTEMS_MILLISECONDS_TO_TICKS( 1000 * TIMED_RETURN_SEC ) + ); + + rtems_mutex_lock( &tiodev.lock ); + if ( tiodev.waiting ) { + rtems_event_send( tiodev.client_id, RTEMS_EVENT_0 ); + } + rtems_mutex_unlock( &tiodev.lock ); + } + + rtems_task_exit(); +} + +int test_iodev_get_device_info( rtems_iodev *iodev, void *event_info ) +{ + (void) iodev; + struct test_iodev_dev_info *info; + + info = event_info; + info->id = TEST_IODEV_TEST_DEV_ID; + + return 0; +} + +int test_iodev_get_event_info( + rtems_iodev *iodev, + struct rtems_iodev_event_info *event_info +) +{ + (void) iodev; + + if ( event_info->index == 0 ) { + event_info->name = "timed_return"; + event_info->args = NULL; + } else if ( event_info->index == 1 ) { + event_info->name = "noreturn"; + event_info->args = NULL; + } else { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + return 0; +} + +int test_iodev_event_wait( + rtems_iodev *iodev, + struct rtems_iodev_event_args *event_args +) +{ + (void) iodev; + + rtems_status_code sc; + rtems_event_set event_in; + rtems_interval timeout; + rtems_event_set event_out; + + if ( event_args->index == 0 ) { + event_in = RTEMS_EVENT_0; + rtems_event_send( tiodev.server_id, RTEMS_EVENT_0 ); + } else if ( event_args->index == 1 ) { + event_in = RTEMS_EVENT_1; + } else { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + if ( event_args->timeout.tv_sec == 0 && event_args->timeout.tv_nsec == 0 ) { + timeout = RTEMS_NO_TIMEOUT; + } else { + timeout = rtems_timespec_to_ticks( &event_args->timeout ); + } + + rtems_mutex_lock( &tiodev.lock ); + tiodev.client_id = rtems_task_self(); + tiodev.waiting = true; + rtems_mutex_unlock( &tiodev.lock ); + + sc = rtems_event_receive( event_in, RTEMS_WAIT, timeout, &event_out ); + + rtems_mutex_lock( &tiodev.lock ); + tiodev.waiting = false; + rtems_mutex_unlock( &tiodev.lock ); + + event_args->timedout = ( sc == RTEMS_TIMEOUT ); + + return 0; +} + +int test_iodev_get_regions( rtems_iodev *iodev, rtems_iodev_region **regions ) +{ + (void) iodev; + + *regions = tiodev.regions; + + return 0; +} + +int test_iodev_get_region_count( rtems_iodev *iodev, size_t *region_count ) +{ + (void) iodev; + + *region_count = TEST_IODEV_REGION_COUNT; + return 0; +} + +int test_iodev_get_event_count( rtems_iodev *iodev, size_t *event_count ) +{ + (void) iodev; + + *event_count = TEST_IODEV_EVENT_COUNT; + return 0; +} + +void test_iodev_priv_destroy( rtems_iodev *iodev ) +{ + (void) iodev; + int i; + + for ( i = 0; i < TEST_IODEV_REGION_COUNT; i++ ) { + free( tiodev.regions[ i ].address ); + } + + rtems_task_delete( tiodev.server_id ); + rtems_mutex_destroy( &tiodev.lock ); +} + +rtems_iodev *test_iodev_init( void ) +{ + rtems_status_code sc; + + tiodev.iodev = rtems_iodev_alloc_and_init( sizeof( rtems_iodev ) ); + + if ( tiodev.iodev == NULL ) { + return NULL; + } + + rtems_mutex_init( &tiodev.lock, "test_iodev" ); + rtems_mutex_lock( &tiodev.lock ); + tiodev.waiting = false; + rtems_mutex_unlock( &tiodev.lock ); + + tiodev.regions[ 0 ].index = 0; + tiodev.regions[ 0 ].address = malloc( TEST_IODEV_REGION_SIZE ); + if ( tiodev.regions[ 0 ].address == NULL ) { + rtems_iodev_destroy_unregistered( tiodev.iodev ); + return NULL; + } + tiodev.regions[ 0 ].size = TEST_IODEV_REGION_SIZE; + tiodev.regions[ 0 ].name = "iodev_region_zero"; + + tiodev.regions[ 1 ].index = 1; + tiodev.regions[ 1 ].address = malloc( TEST_IODEV_REGION_SIZE ); + if ( tiodev.regions[ 1 ].address == NULL ) { + free( tiodev.regions[ 0 ].address ); + rtems_iodev_destroy_unregistered( tiodev.iodev ); + return NULL; + } + tiodev.regions[ 1 ].size = TEST_IODEV_REGION_SIZE; + tiodev.regions[ 1 ].name = "iodev_region_one"; + + sc = rtems_task_create( + rtems_build_name( 'T', 'I', 'O', 'S' ), + 100, + RTEMS_MINIMUM_STACK_SIZE, + RTEMS_DEFAULT_MODES, + RTEMS_DEFAULT_ATTRIBUTES, + &tiodev.server_id + ); + if ( sc != RTEMS_SUCCESSFUL ) { + free( tiodev.regions[ 0 ].address ); + free( tiodev.regions[ 1 ].address ); + rtems_iodev_destroy_unregistered( tiodev.iodev ); + return NULL; + } + + sc = rtems_task_start( + tiodev.server_id, + (rtems_task_entry) test_iodev_server, + (rtems_task_argument) NULL + ); + + tiodev.iodev->get_device_info = test_iodev_get_device_info; + tiodev.iodev->event_wait = test_iodev_event_wait; + tiodev.iodev->get_regions = test_iodev_get_regions; + tiodev.iodev->get_region_count = test_iodev_get_region_count; + tiodev.iodev->get_event_count = test_iodev_get_event_count; + tiodev.iodev->get_event_info = test_iodev_get_event_info; + tiodev.iodev->priv_destroy = test_iodev_priv_destroy; + + return tiodev.iodev; +} diff --git a/testsuites/libtests/iodev01/test_iodev.h b/testsuites/libtests/iodev01/test_iodev.h new file mode 100644 index 0000000000..a98aed8f48 --- /dev/null +++ b/testsuites/libtests/iodev01/test_iodev.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/* + * Copyright (C) 2026 Aaron Nyholm + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __TEST_IODEV_H +#define __TEST_IODEV_H + +#include + +#define TEST_IODEV_REGION_COUNT 2 +#define TEST_IODEV_REGION_SIZE 0x1000 +#define TEST_IODEV_EVENT_COUNT 2 + +#define TEST_IODEV_TEST_DEV_ID 0x12345678 + +#define TEST_IODEV_EVENT_SECS 5 + +struct test_iodev_dev_info { + uint32_t id; +}; + +rtems_iodev *test_iodev_init( void ); + +#endif /* __TEST_IODEV_H */