iodev: Add iodev driver

Add iodev driver and related test
This commit is contained in:
Aaron Nyholm
2026-06-25 17:37:09 -05:00
committed by Kinsey Moore
parent 4a513030e2
commit ed072a2501
10 changed files with 1383 additions and 0 deletions
+353
View File
@@ -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 <dev/io/iodev.h>
#include <rtems/imfs.h>
#include <rtems/score/assert.h>
#include <limits.h>
#include <sys/mman.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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, &region_count );
if ( (size_t) off > region_count ) {
rtems_set_errno_and_return_minus_one( EOVERFLOW );
}
iodev->get_regions( iodev, &regions );
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, &region_count );
if ( region->index >= region_count ) {
rtems_set_errno_and_return_minus_one( EINVAL );
}
iodev->get_regions( iodev, &regions );
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;
}
+360
View File
@@ -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 <time.h>
#include <rtems.h>
#include <rtems/thread.h>
#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 */
+4
View File
@@ -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
+2
View File
@@ -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
@@ -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: []
+305
View File
@@ -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 <rtems/libcsupport.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#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, &region );
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 <rtems/confdefs.h>
+15
View File
@@ -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.
+2
View File
@@ -0,0 +1,2 @@
*** BEGIN OF TEST IODEV 1 ***
*** END OF TEST IODEV 1 ***
+275
View File
@@ -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 <stdlib.h>
#include <stdio.h>
#include <rtems.h>
#include <rtems/seterr.h>
#include <rtems/timespec.h>
#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;
}
+47
View File
@@ -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 <dev/io/iodev.h>
#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 */