summaryrefslogtreecommitdiff
path: root/cpukit
diff options
context:
space:
mode:
authorJoel Sherrill <joel@rtems.org>2023-07-05 15:28:54 -0500
committerJoel Sherrill <joel@rtems.org>2023-08-11 13:44:47 -0500
commitfd693085ea1fcfe41fedb877eac35875cad4aa08 (patch)
treeb5d4b04e53807892c108912c343e7dd7b531838e /cpukit
parent0a766a88d75205622f0d648434aa41b383339253 (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.h502
-rw-r--r--cpukit/include/rtems/regulatorimpl.h135
-rw-r--r--cpukit/libmisc/regulator/regulator.c679
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,
+ &regulator_message,
+ &regulator_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,
+ &regulator_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;
+}