diff options
author | Joel Sherrill <joel@rtems.org> | 2023-07-05 15:28:54 -0500 |
---|---|---|
committer | Joel Sherrill <joel@rtems.org> | 2023-08-11 13:44:47 -0500 |
commit | fd693085ea1fcfe41fedb877eac35875cad4aa08 (patch) | |
tree | b5d4b04e53807892c108912c343e7dd7b531838e /cpukit | |
parent | 0a766a88d75205622f0d648434aa41b383339253 (diff) |
Add the Regulator Interface and test
Updates #4924.
The Regulator is an application support class which is used to
deal with the scenario where there is a bursty input source
which needs to be metered out to a destination sink. The maximum
size of bursts needs to be known and the delivery method must
be configured to deliver messages at a rate that allows the
traffic to not overflow.
Diffstat (limited to 'cpukit')
-rw-r--r-- | cpukit/include/rtems/regulator.h | 502 | ||||
-rw-r--r-- | cpukit/include/rtems/regulatorimpl.h | 135 | ||||
-rw-r--r-- | cpukit/libmisc/regulator/regulator.c | 679 |
3 files changed, 1316 insertions, 0 deletions
diff --git a/cpukit/include/rtems/regulator.h b/cpukit/include/rtems/regulator.h new file mode 100644 index 0000000000..442040a180 --- /dev/null +++ b/cpukit/include/rtems/regulator.h @@ -0,0 +1,502 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RegulatorAPI + * + * @brief This header file defines the Regulator API. + * + */ + +/* + * Copyright (C) 2023 On-Line Applications Research Corporation (OAR) + * + * 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. + */ + +/** + * @defgroup RegulatorAPI Regulator API + * + * @brief Regulator APIs + * + * The Regulator provides a set of APIs to manage input sources which + * produces bursts of message traffic. + * + * The regulator is designed to sit logically between two entities -- a + * source and a destination, where it limits the traffic sent to the + * destination to prevent it from being flooded with messages from the + * source. This can be used to accommodate bursts of input from a source + * and meter it out to a destination. The maximum number of messages + * which can be buffered in the regulator is specified by the + * @a maximum_messages field in the @a rtems_regulator_attributes + * structure passed as an argument to @a rtems_regulator_create(). + * + * The regulator library accepts an input stream of messages from a + * source and delivers them to a destination. The regulator assumes that the + * input stream from the source contains sporadic bursts of data which can + * exceed the acceptable rate of the destination. By limiting the message rate, + * the regulator prevents an overflow of messages. + * + * The regulator can be configured for the input buffering required to manage + * the maximum burst and for the metering rate for the output. The output rate + * is in messages per second. If the sender produces data too fast, the + * regulator will buffer the configured number of messages. + * + * A configuration capability is provided to allow for adaptation to different + * message streams. The regulator can also support running multiple instances, + * which could be used on independent message streams. + * + * The regulator provides a simple interface to the application for avoiding + * bursts of input from a fast source overflowing a slower destination. + * + * It is assumed that the application has a design limit on the number of + * messages which may be buffered. All messages accepted by the regulator, + * assuming no overflow on input, will eventually be output by the Delivery + * thread. + * + * A regulator instance is used as follows from the producer/source side: + * + * @code + * while (1) + * use rtems_regulator_obtain_buffer to obtain a buffer + * input operation to fetch data into the buffer + * rtems_regulator_send(buffer, size of message) + * @endcode + * + * The delivery of message buffers to the Destination and subsequent + * release is performed in the context of the delivery thread by either + * the delivery function or delivery thread. Details are below. + * + * The sequence diagram below shows the interaction between a message Source, + * a Regulator instance, and RTEMS, given the usage described in the above + * paragraphs. + * + * \startuml "Regulator Application Input Source Usage" + * Source -> Regulator : rtems_regulator_obtain_buffer(regulator, buffer) + * Regulator -> RTEMS : rtems_partition_get_buffer(id, buffer) + * RTEMS --> Regulator : rtems_status_code + * Regulator --> Source : rtems_status_code + * Source -> Regulator : rtems_regulator_send(regulator, message, length) + * Regulator -> RTEMS : rtems_message_queue_send(id, message, size) + * RTEMS --> Regulator : rtems_status_code + * Regulator --> Source : rtems_status_code + * \enduml + * + * As illustrated in the sequence diagram, the Source usually corresponds + * to application software reading a system input. The Source obtains a + * buffer from the Regulator instance and fills it with incoming data. + * The application explicitly obtaining a buffer and filling it in allows + * for zero copy operations on the Source side. + * + * The Source then sends the buffer to the Regulator instance. The Regulator + * the sends the buffer via a message queue which to the Delivery thread. + * The Delivery thread executes periodically at a rate specified at + * Regulation creation. At each period, the Delivery thread attempts to + * receive up to a configured number of buffers and invoke the Delivery + * function to deliver them to the Destination. + * + * The Delivery function is provided by the application for this + * specific Regulator instance. Depending on the Destination, it may use + * a function which copies the buffer contents (e.g., write()) or which + * operates directly on the buffer contents (e.g. DMA from buffer). In + * the case of a Destination which copies the buffer contents, the buffer + * can be released via @a rtems_regulator_release_buffer() as soon as the + * function or copying completes. In the case where the delivery uses the + * buffer and returns, the call to @a rtems_regulator_release_buffer() + * will occur when the use of the buffer is complete (e.g. completion + * of DMA transfer). This explicit and deliberate exposure of buffering + * provides the application with the ability to avoid copying the contents. + * + * After the Source has sent the message to the Regulator instance, + * the Source is free to process another input and the Regulator + * instance will ensure that the buffer is delivered to the Delivery + * function and Destination. + * + * The Regulator implementation uses the RTEMS Classic API Partition Manager + * to manage the buffer pool and the RTEMS Classic API Message Queue + * Manager to send the buffer to the Delivery thread. + */ + +#ifndef REGULATOR_H +#define REGULATOR_H + +#include <stdlib.h> + +#include <rtems.h> + +/** + * @ingroup RegulatorAPI + * + * @brief Regulator Delivery Function Type + * + * The user provides a function which is invoked to deliver a message + * to the output. It is invoked by the Delivery thread created as part + * of @a rtems_regulator_create(). The priority and stack size of the + * Delivery thread are specified in the regulator attribute set. + * + * It takes three parameters: + * + * @param[in] context is an untyped pointer to a user context + * @param[in] message points to the message + * @param[in] length is the message size + * + * The following is an example deliverer function. It assumes that the + * application has defined the my_context_t structure and it has at least + * the socket field. The @a message passed in originated with an + * application source which obtained the @a message buffer using + * @a rtems_regulator_obtain_buffer(), filled it in with source data, + * and used @a rtems_regulator_send() to hand to the regulator instance + * for later delivery. + * + * @code + * bool my_deliverer( + * void *context, + * void *message, + * size_t length + * ) + * { + * my_context_t *my_context; + * + * my_context = (my_context_t *)context; + * + * write(my_context->socket, message, length); + * rtems_regulator_release_buffer(message); + * // return false to indicate we released the buffer + * return false; + * } + * @endcode + * + * The delivery function returns true to indicate that the delivery thread + * should release the buffer or false to indicate that it released the + * buffer. If the delivery function invokes a function like @a write() + * to deliver the message to the destination, then the buffer can be + * released immediately after the call. If the delivery function does + * something like setting up a DMA transfer of the buffer, it cannot be + * released until after the DMA is complete. + * + * The following sequence diagram shows the behavior of the Delivery thread + * body and its interaction with the user-supplied deliverer() function. + * + * \startuml "Regulator Delivery Thread Body" + * loop while (1) + * "Delivery Thread" -> RTEMS : rtems_rate_monotonic_period(id, delivery_thread_period) + * loop for 0 : maximum_to_dequeue_per_period + * "Delivery Thread" -> RTEMS : rtems_message_queue_receive(id, message, size, wait, 0) + * RTEMS --> "Delivery Thread" : rtems_status_code + * group if [rtems_status_code != RTEMS_SUCCESSFUL] + * RTEMS -> "Delivery Thread" : break + * end + * "Delivery Thread" -> Application : deliverer(context, buffer, length) + * "Delivery Thread" -> RTEMS : rtems_partition_return_buffer(id, buffer) + * RTEMS --> "Delivery Thread" : rtems_status_code + * end + * end + * \enduml + * + * In the above sequence diagram, the key points are: + * + * -# The Delivery Thread Body is periodically executed. + * -# During each period, up to the instance configuration parameter + * @a maximum_to_dequeue_per_period may be dequeued and + * passed the application's delivery function for processing. + * + * Note that the application explicitly obtains buffers from the + * regulator instance but that the release may be done by Delivery + * Thread, the Delivery function, or later when the buffer contents + * are transferred. + */ +typedef bool (*rtems_regulator_deliverer)( + void *context, + void *message, + size_t length +); + +/** + * @ingroup RegulatorAPI + * + * @brief Attributes for Regulator Instance + * + * An instance of this structure must be populated by the application + * before creating an instance of the regulator. These settings tailor + * the behavior of the regulator instance. + */ +typedef struct { + /** Application function to invoke to output a message to the destination*/ + rtems_regulator_deliverer deliverer; + + /** Context pointer to pass to deliver function */ + void *deliverer_context; + + /** Maximum size message to process */ + size_t maximum_message_size; + + /** Maximum number of messages to be able to buffer */ + size_t maximum_messages; + + /** Priority of Delivery thread */ + rtems_task_priority delivery_thread_priority; + + /** Stack size of Delivery thread */ + size_t delivery_thread_stack_size; + + /** Period (in ticks) of Delivery thread */ + rtems_interval delivery_thread_period; + + /** Maximum messages to dequeue per period */ + size_t maximum_to_dequeue_per_period; + +} rtems_regulator_attributes; + +/** + * @ingroup RegulatorAPI + * + * @brief Statistics for Regulator Instance + * + * An instance of this structure is provided to the directive + * @a rtems_regulator_get_statistics and is filled in by that service. + */ +typedef struct { + /** Number of successfully obtained buffers. */ + size_t obtained; + + /** Number of successfully released buffers. */ + size_t released; + + /** Number of successfully delivered buffers. */ + size_t delivered; + + /** Rate Monotonic Period statistics for Delivery Thread */ + rtems_rate_monotonic_period_statistics period_statistics; + +} rtems_regulator_statistics; + +/** + * @ingroup RegulatorAPI + * + * @brief Regulator Internal Structure + */ +struct _Regulator_Control; + +/** + * @ingroup RegulatorAPI + * + * @brief Regulator Instance + * + * This is used by the application as the handle to a Regulator instance. + */ +typedef struct _Regulator_Control *rtems_regulator_instance; + +/** + * @ingroup RegulatorAPI + * + * @brief Create a regulator + * + * This function creates an instance of a regulator. It uses the provided + * @a attributes to create the instance return in @a regulator. This instance + * will allocate the buffers associated with the regulator instance as well + * as the Delivery thread. + * + * The @a attributes structure defines the priority and stack size of + * the Delivery thread dedicated to this regulator instance. It also + * defines the period of the Delivery thread and the maximum number of + * messages that may be delivered per period via invocation of the + * delivery function. + * + * For each regulator instance, the following resources are allocated: + * + * - A memory area for the regulator control block using @a malloc(). + * - A RTEMS Classic API Message Queue is constructed with message + * buffer memory allocated using @a malloc(). Each message consists + * of a pointer and a length. + * - A RTEMS Classic API Partition. + * - A RTEMS Classic API Rate Monotonic Period. + * + * @param[in] attributes specify the regulator instance attributes + * @param[inout] regulator will point to the regulator instance + * + * @return an RTEMS status code indicating success or failure. + * + * @note This function allocates memory for the buffers holding messages, + * an Delivery thread and an RTEMS partition. When it executes, the + * Delivery thread will create an RTEMS rate monotonic period. + */ +rtems_status_code rtems_regulator_create( + rtems_regulator_attributes *attributes, + rtems_regulator_instance **regulator +); + +/** + * @ingroup RegulatorAPI + * + * @brief Delete a regulator + * + * This function is used to delete the specified @a regulator instance. + * + * It is the responsibility of the user to ensure that any resources + * such as sockets or open file descriptors used by the delivery + * function are also deleted. It is likely safer to delete those + * delivery resources after deleting the regulator instance rather than + * before. + * + * @param[in] regulator is the instance to delete + * @param[in] ticks is the maximum number of ticks to wait for + * the delivery thread to shutdown. + * + * @return an RTEMS status code indicating success or failure. + * + * @note This function deallocates the resources allocated during + * @a rtems_regulator_create(). + */ +rtems_status_code rtems_regulator_delete( + rtems_regulator_instance *regulator, + rtems_interval ticks +); + +/** + * @ingroup RegulatorAPI + * + * @brief Obtain Buffer from Regulator + * + * This function is used to obtain a buffer from the regulator's pool. The + * @a buffer returned is assumed to be filled in with contents and used + * in a subsequent call to @a rtems_regulator_send(). When the @a buffer is + * delivered, it is expected to be released. If the @a buffer is not + * successfully accepted by this function, then it should be returned + * using @a rtems_regulator_release_buffer() or used to send another message. + * + * The @a buffer is of the maximum_message_size specified in the attributes + * passed in to @a rtems_regulator_create(). + * + * @param[in] regulator is the regulator instance to operate upon + * @param[out] buffer will point to the allocated buffer + * + * @return an RTEMS status code indicating success or failure. + * + * @note This function does not perform dynamic allocation. It obtains a + * buffer from the pool allocated during @a rtems_regulator_create(). + * + * @note Any attempt to write outside the buffer area is undefined. + */ +rtems_status_code rtems_regulator_obtain_buffer( + rtems_regulator_instance *regulator, + void **buffer +); + +/** + * @ingroup RegulatorAPI + * + * @brief Release Previously Obtained Regulator Buffer + * + * This function is used to release a buffer to the regulator's pool. It is + * assumed that the @a buffer returned will not be used by the application + * anymore. The @a buffer must have previously been allocated by + * @a rtems_regulator_obtain_buffer() and NOT passed to + * @a rtems_regulator_send(). + * + * If a subsequent @a rtems_regulator_send() using this @a buffer is + * successful, the @a buffer will eventually be processed by the delivery + * thread and released. + * + * @param[in] regulator is the regulator instance to operate upon + * @param[out] buffer will point to the buffer to release + * + * @return an RTEMS status code indicating success or failure. + * + * @note This function does not perform dynamic deallocation. It releases a + * buffer to the pool allocated during @a rtems_regulator_create(). + */ +rtems_status_code rtems_regulator_release_buffer( + rtems_regulator_instance *regulator, + void *buffer +); + +/** + * @ingroup RegulatorAPI + * + * @brief Send to regulator instance + * + * This function is used by the producer to send a @a message to the + * @a regulator for later delivery by the Delivery thread. The message is + * contained in the memory pointed to by @a message and is @a length + * bytes in length. + * + * It is required that the @a message buffer was obtained via + * @a rtems_regulator_obtain_buffer(). + * + * It is assumed that the @a message buffer has been filled in with + * application content to deliver. + * + * If the @a rtems_regulator_send() is successful, the buffer is enqueued + * inside the regulator instance for subsequent delivery. After the + * @a message is delivered, it may be released by either delivery + * function or the application code depending on the implementation. + * + * The status @a RTEMS_TOO_MANY is returned if the regulator's + * internal queue is full. This indicates that the configured + * maximum number of messages was insufficient. It is the + * responsibility of the caller to decide whether to hold messages, + * drop them, or print a message that the maximum number of messages + * should be increased. + * + * If @a rtems_regulator_send() is unsuccessful, it is the application's + * responsibility to release the buffer. If it is successfully sent, + * then it becomes the responsibility of the delivery function to + * release it. + * + * @param[in] regulator is the regulator instance to operate upon + * @param[out] message points to the message to deliver + * @param[out] length is the size of the message in bytes + * + * @return an RTEMS status code indicating success or failure. + * + */ +rtems_status_code rtems_regulator_send( + rtems_regulator_instance *regulator, + void *message, + size_t length +); + +/** + * @ingroup RegulatorAPI + * + * @brief Obtain statistics for regulator instance + * + * This function is used by the application to obtain statistics + * information about the regulator instance. + * + * If the @a obtained and @a released fields in the returned + * @a statistics structure are equal, then there are no buffers + * outstanding from this regulator instance. + * + * @param[in] regulator is the regulator instance to operate upon + * @param[inout] statistics points to the statistics structure to fill in + * + * @return an RTEMS status code indicating success or failure. + * + */ +rtems_status_code rtems_regulator_get_statistics( + rtems_regulator_instance *regulator, + rtems_regulator_statistics *statistics +); + +#endif /* REGULATOR_H */ diff --git a/cpukit/include/rtems/regulatorimpl.h b/cpukit/include/rtems/regulatorimpl.h new file mode 100644 index 0000000000..a5652a764d --- /dev/null +++ b/cpukit/include/rtems/regulatorimpl.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RegulatorInternalAPI + * + * @brief Regulator Library Implementation Support + */ + +/* + * Copyright (C) 2023 On-Line Applications Research Corporation (OAR) + * + * 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. + */ + +/** + * @defgroup RegulatorInternalAPI Regulator API Internals + * + * @brief Regulator Internal Information + * + * This concerns implementation information about the Regulator. + */ + +#ifndef RTEMS_REGULATORIMPL_H +#define RTEMS_REGULATORIMPL_H + +#include <stdatomic.h> + +#include <rtems/chain.h> + + +/** + * @ingroup RegulatorInternalAPI + * + * This constant is used to indicate the regulator instance is initialized. + */ +#define REGULATOR_INITIALIZED 0xDeadF00d + +/** + * @ingroup RegulatorInternalAPI + * + * @brief Regulator Message Instance Management Structure + */ +typedef struct { + /** This points to the message contents. */ + void *buffer; + /** This is the length of the message. */ + size_t length; +} _Regulator_Message_t; + +/** + * @ingroup RegulatorInternalAPI + * + * @brief Regulator Statistics Private Structure + * + * An instance of this structure is allocated per regulator instance. + */ +typedef struct { + /** Number of successfully obtained buffers. */ + atomic_size_t obtained; + + /** Number of successfully released buffers. */ + atomic_size_t released; + + /** Number of successfully delivered buffers. */ + atomic_size_t delivered; +} _Regulator_Statistics; + +/** + * @ingroup RegulatorInternalAPI + * + * @brief Regulator Instance Private Structure + * + * An instance of this structure is allocated per regulator instance. + */ +typedef struct { + /** Has magic value when instance is usable */ + uint32_t initialized; + + /** Attributes for this instance -- copied from user */ + rtems_regulator_attributes Attributes; + + /** Pointer to allocated message memory */ + void *message_memory; + + /** Pointer to allocated memory for RTEMS Message Queue for pending buffers*/ + void *message_queue_storage; + + /** RTEMS Message Queue of pending outgoing messages */ + rtems_id queue_id; + + /** RTEMS Partition for pool of unused messages */ + rtems_id messages_partition_id; + + /** RTEMS Task for performing output */ + rtems_id delivery_thread_id; + + /** Id of period used by output thread */ + rtems_id delivery_thread_period_id; + + /** Indicates Delivery thread is running */ + bool delivery_thread_is_running; + + /** Indicates Delivery thread has been requested to exit */ + bool delivery_thread_request_exit; + + /** Indicates Delivery thread has exited */ + bool delivery_thread_has_exited; + + /** Internal Statistics */ + _Regulator_Statistics Statistics; + +} _Regulator_Control; + +#endif /* RTEMS_REGULATORIMPL_H */ diff --git a/cpukit/libmisc/regulator/regulator.c b/cpukit/libmisc/regulator/regulator.c new file mode 100644 index 0000000000..2071c468fd --- /dev/null +++ b/cpukit/libmisc/regulator/regulator.c @@ -0,0 +1,679 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @brief Regulator Library Implementation + */ + +/* + * Copyright (C) 2022 On-Line Applications Research Corporation (OAR) + * + * 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 <rtems.h> +#include <rtems/regulator.h> + +#include <rtems/regulatorimpl.h> + +/** + * @ingroup RegulatorInternalAPI + * + * This method is the body for the task which delivers the output for + * this regulator instance at the configured rate. + * + * @param[in] arg points to the regulator instance this thread + * is associated with + * + * @note The argument passed in cannot be NULL if the + * rtems_regulator_create worked. + */ +static rtems_task _Regulator_Output_task_body( + rtems_task_argument arg +) +{ + _Regulator_Control *the_regulator = (_Regulator_Control *)arg; + rtems_status_code sc; + size_t to_dequeue; + _Regulator_Message_t regulator_message; + size_t regulator_message_size; + bool release_it; + + the_regulator->delivery_thread_is_running = true; + + /** + * This thread uses a rate monotonic period object instance. A rate + * monotonic period object must be created by the thread using it. + * It can be deleted by any thread which simplifies clean up. + * + * The rate_monotonic_create() call can fail if the application + * is incorrectly configured. This thread has no way to report the + * failure. If it continues with an invalid id, then the thread will + * not block on the period and spin continuously consuming CPU. The only + * alternatives are to invoke rtems_fatal_error_occurred() or silently + * exit the thread. + */ + sc = rtems_rate_monotonic_create( + rtems_build_name('P', 'E', 'R', 'D'), + &the_regulator->delivery_thread_period_id + ); + if (sc != RTEMS_SUCCESSFUL) { + goto exit_delivery_thread; + } + + /** + * Loop on the rate_monotonic_period() based on the specified period. + */ + while (1) { + sc = rtems_rate_monotonic_period( + the_regulator->delivery_thread_period_id, + the_regulator->Attributes.delivery_thread_period + ); + _Assert_Unused_variable_equals(sc, RTEMS_SUCCESSFUL); + + /** + * If the delivery thread has been requested to exit, then + * quit processing messages, break out of this loop, and exit + * this thread. + */ + if (the_regulator->delivery_thread_request_exit) { + break; + } + + /** + * Loop for the configured number of messages to deliver per period. + * If we reach the point, there are no more messages, block for the + * rest of this period. If there are messages, deliver them. + */ + for (to_dequeue = 0; + to_dequeue < the_regulator->Attributes.maximum_to_dequeue_per_period; + to_dequeue++) { + regulator_message_size = sizeof(_Regulator_Message_t); + sc = rtems_message_queue_receive( + the_regulator->queue_id, + ®ulator_message, + ®ulator_message_size, + RTEMS_NO_WAIT, + 0 + ); + _Assert_Unused_variable_equals(sc, RTEMS_SUCCESSFUL); + if (sc != RTEMS_SUCCESSFUL) { + break; + } + + release_it = the_regulator->Attributes.deliverer( + the_regulator->Attributes.deliverer_context, + regulator_message.buffer, + regulator_message.length + ); + + the_regulator->Statistics.delivered++; + + /** + * The message was successfully delivered. If the delivery function + * wants the buffer returned, do it now. The delivery to the Destination + * may involve handing the buffer off to something like DMA + * and need to wait for it to complete before releasing the buffer. + * + * Note that this is the underlying RTEMS service + * used by @a rtems_regulator_obtain_buffer() and @a + * rtems_regulator_release_buffer(). + */ + if (release_it == true) { + the_regulator->Statistics.released++; + sc = rtems_partition_return_buffer( + the_regulator->messages_partition_id, + regulator_message.buffer + ); + _Assert_Unused_variable_equals(sc, RTEMS_SUCCESSFUL); + } + } + } + + /** + * This thread was requested to exit. Do so. + */ +exit_delivery_thread: + the_regulator->delivery_thread_is_running = false; + the_regulator->delivery_thread_has_exited = true; + + (void) rtems_rate_monotonic_delete(the_regulator->delivery_thread_period_id); + + rtems_task_exit(); +} + +/** + * @ingroup RegulatorInternalAPI + * + * This method frees the resources associated with a regulator instance. + * The resources are freed in the opposite of the order in which they are + * allocated. This is used on error cases in @a rtems_regulator_create() and in + * @a rtems_regulator_delete(). + * + * @param[in] the_regulator is the instance to operate upon + * @param[in] ticks is the length of time to wait for the delivery thread + * to exit + * + * @return This method returns true is successful and false on timeout. + */ +static bool _Regulator_Free_helper( + _Regulator_Control *the_regulator, + rtems_interval ticks +) +{ + rtems_status_code sc; + + + /* + * If the output thread has not started running, then we can just delete it. + */ + + if (ticks == 0 || the_regulator->delivery_thread_is_running == false) { + sc = rtems_task_delete(the_regulator->delivery_thread_id); + _Assert_Unused_variable_equals(sc, RTEMS_SUCCESSFUL); + } else { + rtems_interval remaining = ticks; + + the_regulator->delivery_thread_request_exit = true; + + while (1) { + if (the_regulator->delivery_thread_has_exited) { + break; + } + + if (remaining == 0) { + return false; + } + + (void) rtems_task_wake_after(1); + remaining--; + } + } + + /* + * The output thread deletes the rate monotonic period that it created. + */ + + /* + * The regulator's message_queue_storage is implicitly freed by this call. + */ + sc = rtems_message_queue_delete(the_regulator->queue_id); + _Assert_Unused_variable_equals(sc, RTEMS_SUCCESSFUL); + + sc = rtems_partition_delete(the_regulator->messages_partition_id); + _Assert_Unused_variable_equals(sc, RTEMS_SUCCESSFUL); + + if (the_regulator->message_memory) { + free(the_regulator->message_memory); + } + + the_regulator->initialized = 0; + free(the_regulator); + return true; +} + +/** + * @ingroup RegulatorInternalAPI + */ +rtems_status_code rtems_regulator_create( + rtems_regulator_attributes *attributes, + rtems_regulator_instance **regulator +) +{ + _Regulator_Control *the_regulator; + rtems_status_code sc; + size_t alloc_size; + + /** + * Perform basic validation of parameters + */ + if (attributes == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + if (regulator == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + /** + * Verify attributes are OK. Some are checked by calls to object create + * methods. Specifically the following are not checked: + * + * - delivery_thread_priority by rtems_task_create() + * - delivery_thread_stack_size can be any value + */ + if (attributes->deliverer == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + if (attributes->maximum_messages == 0) { + return RTEMS_INVALID_NUMBER; + } + + if (attributes->maximum_message_size == 0) { + return RTEMS_INVALID_SIZE; + } + + if (attributes->maximum_to_dequeue_per_period == 0) { + return RTEMS_INVALID_NUMBER; + } + + if (attributes->delivery_thread_period == 0) { + return RTEMS_INVALID_NUMBER; + } + + /** + * Allocate memory for regulator instance + */ + the_regulator = (_Regulator_Control *) calloc(sizeof(_Regulator_Control), 1); + if (the_regulator == NULL) { + return RTEMS_NO_MEMORY; + } + + /** + * We do NOT want the delivery_thread_id field to be initialized to 0. If the + * @a rtems_task_create() fails, then the field will not be overwritten. + * This results in an attempt to rtems_task_delete(0) during clean + * up. The thread ID of 0 is self which results in the calling thread + * accidentally deleting itself. + */ + the_regulator->delivery_thread_id = (rtems_id) -1; + + /** + * Copy the attributes to an internal area for later use + */ + the_regulator->Attributes = *attributes; + + /** + * Allocate memory for the messages. There is no need to zero out the + * message memory because the user should fill that in. + */ + alloc_size = attributes->maximum_message_size * attributes->maximum_messages; + the_regulator->message_memory = calloc(alloc_size, 1); + if (the_regulator->message_memory == NULL) { + _Regulator_Free_helper(the_regulator, 0); + return RTEMS_NO_MEMORY; + } + + /** + * Associate message memory with a partition so allocations are atomic + */ + sc = rtems_partition_create( + rtems_build_name('P', 'O', 'O', 'L'), + the_regulator->message_memory, + alloc_size, + attributes->maximum_message_size, + RTEMS_DEFAULT_ATTRIBUTES, + &the_regulator->messages_partition_id + ); + if (sc != RTEMS_SUCCESSFUL) { + _Regulator_Free_helper(the_regulator, 0); + return sc; + } + + /** + * Create the message queue between the sender and output thread + */ + RTEMS_MESSAGE_QUEUE_BUFFER(sizeof(_Regulator_Message_t)) regulator_message_t; + + size_t storage_size = sizeof(regulator_message_t) * attributes->maximum_messages; + + the_regulator->message_queue_storage = malloc(storage_size); + if (the_regulator->message_queue_storage == NULL) { + _Regulator_Free_helper(the_regulator, 0); + return RTEMS_NO_MEMORY; + } + + rtems_message_queue_config mq_config = { + .name = rtems_build_name('S', 'N', 'D', 'Q'), + .maximum_pending_messages = attributes->maximum_messages, + .maximum_message_size = sizeof(_Regulator_Message_t), + .storage_area = the_regulator->message_queue_storage, + .storage_size = storage_size, + .storage_free = free, + .attributes = RTEMS_DEFAULT_ATTRIBUTES + }; + sc = rtems_message_queue_construct( + &mq_config, + &the_regulator->queue_id + ); + if (sc != RTEMS_SUCCESSFUL) { + _Regulator_Free_helper(the_regulator, 0); + return sc; + } + + /** + * @note A rate monotonic period object must be created by the thread + * using it. Thus that specific create operation is not included + * in this method. All other resources are allocated here. + */ + + /** + * Create the output thread Using the priority and stack size attributes + * specified by the user. + */ + sc = rtems_task_create( + rtems_build_name('R', 'E', 'G', 'U'), + attributes->delivery_thread_priority, + attributes->delivery_thread_stack_size, + RTEMS_DEFAULT_MODES, + RTEMS_DEFAULT_ATTRIBUTES, + &the_regulator->delivery_thread_id + ); + if (sc != RTEMS_SUCCESSFUL) { + _Regulator_Free_helper(the_regulator, 0); + return sc; + } + + /** + * Start the output thread. + * + * @note There should be no way this call can fail. The task id is valid, + * the regulator output thread entry point is valid, and the argument + * is valid. + */ + the_regulator->delivery_thread_is_running = true; + the_regulator->delivery_thread_request_exit = false; + the_regulator->delivery_thread_has_exited = false; + + sc = rtems_task_start( + the_regulator->delivery_thread_id, + _Regulator_Output_task_body, + (rtems_task_argument) the_regulator + ); + _Assert_Unused_variable_equals(sc, RTEMS_SUCCESSFUL); + + /** + * The regulator is successfully initialized. Set the initialized field + * to reflect this and return the instance pointer. + */ + the_regulator->initialized = REGULATOR_INITIALIZED; + + *regulator = (void *)the_regulator; + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Validate the regulator instance provided by the user + * + * Validate the regulator instance provided by the user + * + * @param[in] regulator is the instance provided by the user + * @param[inout] status will contain the RTEMS status for this check + * + * @return This method returns a @a _Regulator_Control instance pointer + * which is NULL if invalid or points to the internal regulator + * control structure if valid. + */ +static inline _Regulator_Control *_Regulator_Get( + rtems_regulator_instance *regulator, + rtems_status_code *status +) +{ + _Regulator_Control *the_regulator = (_Regulator_Control *) regulator; + + if (the_regulator == NULL) { + *status = RTEMS_INVALID_ADDRESS; + return NULL; + } + + if (the_regulator->initialized != REGULATOR_INITIALIZED) { + *status = RTEMS_INCORRECT_STATE; + return NULL; + } + + status = RTEMS_SUCCESSFUL; + return the_regulator; +} + +/** + * @ingroup RegulatorInternalAPI + */ +rtems_status_code rtems_regulator_delete( + rtems_regulator_instance *regulator, + rtems_interval ticks +) +{ + _Regulator_Control *the_regulator; + rtems_status_code status; + + /** + * Convert external handle to internal instance pointer + */ + the_regulator = _Regulator_Get(regulator, &status); + if (the_regulator == NULL) { + return status; + } + + /** + * There can be no buffers outstanding + */ + _Regulator_Statistics *stats = &the_regulator->Statistics; + + if (stats->obtained != stats->released ) { + return RTEMS_RESOURCE_IN_USE; + } + + /** + * Free the resources associated with this regulator instance. + */ + bool bc; + bc = _Regulator_Free_helper(the_regulator, ticks); + if (bc == false) { + return RTEMS_TIMEOUT; + } + + return RTEMS_SUCCESSFUL; +} + +/** + * @ingroup RegulatorInternalAPI + * + * Allocate a buffer for the caller using the internal partition. + */ +rtems_status_code rtems_regulator_obtain_buffer( + rtems_regulator_instance *regulator, + void **buffer +) +{ + _Regulator_Control *the_regulator; + rtems_status_code status; + + /** + * Convert external handle to internal instance pointer + */ + the_regulator = _Regulator_Get(regulator, &status); + if (the_regulator == NULL) { + return status; + } + + /** + * Allocate a buffer for the user application from the buffer pool managed + * by an Classic API partition. + */ + status = rtems_partition_get_buffer( + the_regulator->messages_partition_id, + buffer + ); + + if (status == RTEMS_SUCCESSFUL) { + the_regulator->Statistics.obtained++; + } + + return status; +} + +/** + * @ingroup RegulatorInternalAPI + * + * Allocate a buffer for the caller using the internal partition. + */ +rtems_status_code rtems_regulator_release_buffer( + rtems_regulator_instance *regulator, + void *buffer +) +{ + _Regulator_Control *the_regulator; + rtems_status_code status; + + /** + * Convert external handle to internal instance pointer + */ + the_regulator = _Regulator_Get(regulator, &status); + if (the_regulator == NULL) { + return status; + } + + /** + * Deallocate the buffer to the buffer pool managed by a Classic + * API partition. + */ + status = rtems_partition_return_buffer( + the_regulator->messages_partition_id, + buffer + ); + + if (status == RTEMS_SUCCESSFUL) { + the_regulator->Statistics.released++; + } + + return status; +} + +/** + * @ingroup RegulatorInternalAPI + */ +rtems_status_code rtems_regulator_send( + rtems_regulator_instance *regulator, + void *message, + size_t length +) +{ + _Regulator_Control *the_regulator; + rtems_status_code status; + _Regulator_Message_t regulator_message; + + the_regulator = (_Regulator_Control *) regulator; + + /** + * Validate the arguments and ensure the regulator was successfully + * initialized. + */ + if (message == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + if (length == 0) { + return RTEMS_INVALID_NUMBER; + } + + /** + * Convert external handle to internal instance pointer + */ + the_regulator = _Regulator_Get(regulator, &status); + if (the_regulator == NULL) { + return status; + } + + /** + * Place the message pointer and length into a temporary structure. This + * lets the implementation internally send the message by reference and + * have a zero-copy implementation. + */ + regulator_message.buffer = message; + regulator_message.length = length; + + /** + * Send the application message to the output thread for delivery using + * a Classic API message queue. + */ + status = rtems_message_queue_send( + the_regulator->queue_id, + ®ulator_message, + sizeof(_Regulator_Message_t) + ); + if (status != RTEMS_SUCCESSFUL) { + return status; + } + + return status; +} + +/** + * @ingroup RegulatorInternalAPI + */ +rtems_status_code rtems_regulator_get_statistics( + rtems_regulator_instance *regulator, + rtems_regulator_statistics *statistics +) +{ + _Regulator_Control *the_regulator; + rtems_status_code status; + + /** + * Validate the arguments and ensure the regulator was successfully + * initialized. + */ + if (statistics == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + /** + * Convert external handle to internal instance pointer + */ + the_regulator = _Regulator_Get(regulator, &status); + if (the_regulator == NULL) { + return status; + } + + /** + * Zero out the statistics structure in case the get period statistics + * fails below. + */ + memset(statistics, 0, sizeof(rtems_regulator_statistics)); + + /** + * Fill in the caller's statistics structure from information + * maintained by the regulator instance about buffers processed. + */ + statistics->obtained = the_regulator->Statistics.obtained; + statistics->released = the_regulator->Statistics.released; + statistics->delivered = the_regulator->Statistics.delivered; + + /** + * Attempt to retrieve the delivery thread's period's statistics. + * + * NOTE; If the Delivery Thread has not run yet, the period will not + * exist yet. We should not fail for this reason but it is why + * we zeroed out the entire structure above. + */ + (void) rtems_rate_monotonic_get_statistics( + the_regulator->delivery_thread_period_id, + &statistics->period_statistics + ); + + return RTEMS_SUCCESSFUL; +} |