Signal Sets

Within every embedded software application, certain events occur within the system that requires the application to respond. In a traditional non-os application, this would be achieved by a super forever loop that continually searches (polling) for events that have occurred. Although this approach is simple to code, it quickly becomes inefficient and difficult to follow and manage as an application handles more and more events. Signals are the elegant and efficient way to handle internal and external system events.

Each signal set has the ability to receive up to 32 unique signals (SIGNAL_0 to SIGNAL_31). Any code, from any context, including interrupts, can set signals within a signal set. A thread can control how the signals are received and what actions are taken when a signal is received.

Waiting for a Signal

A powerful component of a signal is the way that a thread can block and wait for one or more signals to arrive. This functionality gives the application a way of not wasting any CPU cycles until there is actual work for the thread to perform. Utilizing this ability produces the most efficient and responsive applications possible.

A thread can block and wait for a single signal using SIGNAL_WaitOne() or it can wait for multiple signals using SIGNAL_Wait(). Both calls allow the thread to specify a timeout interval so that it can continue on in the event the signal(s) do not arrive. The thread can also specify a zero timeout interval to receive the signals without actually blocking or waiting.

Sending and Consuming Signals

When a signal set is initialized, each of it's 32 possible signals are initialized to a cleared state. At anytime, an application can send one or more signals to a set using SIGNAL_Set(). When a signal is set, a thread may or may not be waiting for the signal. If a thread happens to be waiting for the signal, the signal will immediately be consumed (reset to cleared state) and the waiting thread will be released. If no thread happens to be waiting for the signal, the signal will go into the pending state. If a thread were to enter a wait upon a pending signal, the wait call will return without blocking and the signal will then be consumed (reset to the cleared state). Since the signals support going into a pending state, it does not matter if the signal was sent just before, or just after a wait call, thus removing any potential for race conditions. A signal can be manually cleared using SIGNAL_Clear(). This can be useful when it is necessary for logic to ignore all signals that have been sent prior to a specific point in time. The signals can be queried, without changing their state, using SIGNAL_Query().

Basic Usage

The code snippet below shows an event or ISR that signals a waiting thread. The waiting thread is blocked, not consuming any CPU cycles, until the event or ISR occurs.

#include "Kernel/kernel_signal.h"
#include <assert.h>

static SIGNALSET sigset;   /* Allocate a signal set */


/* An example thread function */
void APP_Thread(void* arg)
{
    STATUS status;


    SIGNAL_Init(&signals);                      /* Initialize the signal set */

    for (;;) {                                  /* Thread forever loop */

        status = SIGNAL_WaitOne(&sigset,        /* Wait for single signal */
                                SIGNAL_0,       /* Wait for signal number 0 */
                                INFINITE);      /* Wait indefinitely */

        if (status == SUCCESS) {                /* Was the signal received? */

            /* The event or ISR has occurred */
        }
    }
}

/* An example event or ISR */
void APP_EventOrISR(void)
{
    STATUS status;


    status = SIGNAL_Set(&signals, SIGNAL_0);    /* Set the signal for the waiting thread */
    assert(status == SUCCESS);
}

The code snippet below shows a thread waiting until either signal number 0 or signal number 1 arrives. The snippet uses the returned signals from the wait call to determine which of the signals has caused the wait call to exit.

/* An example thread function */
void APP_Thread(void* arg)
{
    STATUS status;
    UINT32 signals;


    for (;;) {

        status = SIGNAL_Wait(&sigset,               /* Wait for multiple signals */
                             OPT_WAITALL,           /* Wait until ALL of the signals arrive */
                             SIGNAL_0 | SIGNAL_1,   /* Wait for both signal 0 and 1 */
                             &signals,              /* A value to accept the received signals */
                             INFINITE);             /* Wait indefinitely */

        if (status == SUCCESS) {                    /* Did all of the signals arrive? */

            /* Both signals have arrived */
        }
    }
}

Advanced Usage

The kernel allows for complex signaling scenarios. A code snippet below shows a more complex scenario where a thread is waiting for several signals to arrive.

/* An example thread function */
void APP_Thread(void* arg)
{
    STATUS status;
    UINT32 signals;


    for (;;) {

        status = SIGNAL_Wait(&sigset,                           /* Wait for multiple signals */
                             OPT_WAITANY,                       /* Wait until any signals arrive (set) */
                             SIGNAL_0 | SIGNAL_1 | SIGNAL_2,    /* Signal numbers 0, 1, and 2 */
                             &signals,                          /* Receive the state of the signals */
                             10);                               /* Wait for up to 10 ticks */

        if (status == SUCCESS) {                                /* Was at least one signal received? */

            if (signals & SIGNAL_0) {

			    /* Signal number 0 received */
            }

            if (signals & SIGNAL_1) {

			    /* Signal number 1 received */
            }
                
            if (signals & SIGNAL_2) {

			    /* Signal number 2 received */
            }
        }
    }
}

A single thread can have multiple wait statements that work with independent signals without interfering with each other. This is particularly useful since it allows the thread's wait statements to occur in a different order than which the signals are sent to the thread.

The code snippet below shows a thread that calls upon a sub-function that is performing it's own logic with an independent signal.

/* An example thread function */
void APP_Thread(void* arg)
{
    STATUS status;
    UINT32 signals;


    for (;;) {                                          /* Thread forever loop */

        APP_SubFunction();                              /* Call on some sub-function */


        /* The clear signal call is used to reset the signal
        in the event that the signal was sent while running
        the sub function above. This is not mandatory, but
        gives the ability to create a window in time of which
        the signal must be received. */

        status = SIGNAL_Clear(&sigset, SIGNAL_0);       /* Discard any previously received #0 signals */
        if (status == SUCCESS) {

            status = SIGNAL_WaitOne(&sigset,
                                    SIGNAL_0,           /* Wait for signal number 0 */
                                    10);                /* Only wait for up to 10 ticks */
            
            if (status == SUCCESS) {                    /* Was the signal received? */
                
                /* Event 0 has occurred */
            }
        }
    }
}

/* A sub-function that uses another signal */
void APP_SubFunction(void)
{
    STATUS status;


    status = SIGNAL_WaitOne(&sigset,
                            SIGNAL_1,       /* Wait for signal number 1 */
                            20);            /* Only wait for up to 20 ticks */

    if (status == SUCCESS) {                /* Was the signal received? */
        
        /* Event 1 has occurred */
    }
}

/* An example event handler for Event 0 */
void APP_OnEvent0(void)
{
    SIGNAL_Set(&sigset, SIGNAL_0);
}

/* An example event handler for Event 1 */
void APP_OnEvent1(void)
{
    SIGNAL_Set(&sigset, SIGNAL_1);
}

Limitations

Only one thread at a time can block and wait upon a signal set.

API Reference

STATUS SIGNAL_Init(SIGNALSET* set)
Initializes a signal set.
PARAMETERS
set A pointer to the signal set to be initialized.
RETURNS
SUCCESS The given signal set has been initialized.
ERR_NULLREFERENCE The argument 'set' was found to be NULL.
STATUS SIGNAL_Query(const SIGNALSET* set, UINT32* signals)
Returns the state of the signals for a signal set. The signals are returned as a bit-field of signals where each bit number corresponds to the signal number. A bit value of '0' indicates the particular signal is cleared; while a bit value of '1' indicates the signal is currently pending.
PARAMETERS
set A pointer to the signal set that contains the signals to be queried.
signals A pointer to a caller allocated value to accept the returned state of the caller's signals.
RETURNS
SUCCESS The state of the signals have been returned.
ERR_NULLREFERENCE The argument 'set' or 'signals' was found to be NULL.
STATUS SIGNAL_Clear(SIGNALSET* set, UINT32 signals)
Clears one or more signals within a signal set.
PARAMETERS
set A pointer to the signal set that contains the signals to be cleared.
signals The signal(s) to be cleared. Use a bitwise OR to clear multiple signals. e.g. (SIGNAL_0 | SIGNAL_1) to clear both signal number 0 and signal number 1.
RETURNS
SUCCESS The specified signals have been cleared.
ERR_NULLREFERENCE The argument 'set' was found to be a NULL reference.
STATUS SIGNAL_Set(SIGNALSET* set, UINT32 signals)
Sets one or more signals within a signal set. If a thread happens to be waiting for the signal(s), the signal(s) will be consumed and reset back to the cleared state and the waiting thread will be released. If no thread is waiting for the signal(s), the signal(s) will go into the pending state.
PARAMETERS
set A pointer to the signal set to receive the signal(s).
signals One or more signals to be sent to the signal set. Use a bitwise OR to send multiple signals, e.g. (SIGNAL_0 | SIGNAL_1).
RETURNS
SUCCESS The signals were successfully set within the signal set.
ERR_NULLREFERENCE The argument 'set' was found to be NULL.
STATUS SIGNAL_WaitOne(SIGNALSET* set, UINT32 signal, UINT32 timeout)
Waits for a single signal to be received by a signal set. If the specified signal is already in the pending state, this will return immediately without blocking. If the signal is successfully received, the signal is consumed and reset back to the cleared state.
PARAMETERS
set A pointer to the signal set to wait upon to receive the signal.
signal The signal to wait on to be received.
timeout The maximum amount of time, in kernel ticks, to wait for the signal to be received. Use '0' to return immediately without blocking and use INFINITE to wait indefinitely.
RETURNS
SUCCESS The specified signal has been received.
ERR_NULLREFERENCE The argument 'set' was found to be a NULL reference.
ERR_INVALIDCONTEXT The operation is not supported from the context of an interrupt service routine (ISR).
ERR_INVALIDARGUMENT An invalid argument was found. Only a single signal can be specified.
ERR_TIMEOUT The maximum allowable time has elapsed prior to the specified signal being received.
STATUS SIGNAL_Wait(SIGNALSET* set, UINT32 opt, UINT32 mask, UINT32* signals, UINT32 timeout)
Waits for multiple signals to be received by a signal set. If the specified signal(s) are already in the pending state, this will return immediately without blocking. If the signal(s) are successfully received, the signal(s) are consumed and reset back to the cleared state.
PARAMETERS
set A pointer to the signal set to wait upon to receive the signal.
opt An option for waiting.
OPT_WAITANY Wait for ANY of the specified signals to arrive.
OPT_WAITALL Wait for ALL of the specified signals to arrive.
mask The signal(s) to wait on to be received. Use bitwise OR for multiple signals, e.g. (SIGNAL_0 | SIGNAL_1).
signals A pointer to a caller allocated value to receive the state of the signals when the wait operation completed. Can be NULL if don't care.
timeout The maximum amount of time, in kernel ticks, to wait for any of the specified signals to be received. Use '0' to return immediately without blocking and use INFINITE to wait indefinitely.
RETURNS
SUCCESS If OPT_WAITANY, at least one, otherwise all signal(s) have been received.
ERR_NULLREFERENCE The argument 'set' was found to be a NULL reference.
ERR_INVALIDCONTEXT The operation is not supported from the context of an interrupt service routine (ISR).
ERR_TIMEOUT The maximum allowable time has elapsed prior to the specified signals being received.