Events

One of the most powerful features in ChibiOS/RT is the Events subsystem. Events address a specific class of problems:

  • Wait for multiple events. Other primitives only wait for a specific event.
  • Multiple threads, each one interested in one or more events, in a many-to-many relation.
  • Events are asynchronously broadcasted but synchronously checked, threads decide when to check for events or wait for events.

Global Settings

CH_CFG_USE_EVENTS This switch enables the mailboxes API in the kernel.
CH_CFG_USE_EVENTS_TIMEOUT Enables the timeout support for events.

Description

There are several classes of objects involved in events handling: Event Sources, Event Listeners, Event Flags and threads themselves.

Event Sources

Event Sources are the objects that broadcast events to system.

Event Sources

There are two possible operations on event sources:

  • Register. Registering on a source causes the thread to be notified of events.
  • Broadcast. Broadcasting a source notifies all threads registered on the source that an event occurred.

Event Flags

Events also carry information, the information is encoded as Event Flags, a mask of flags of type eventflags_t are broadcasted by sources together with the event itself.
For example, an event source is associated to a serial driver in order to signal events on the line, the flags represent the line condition: data transmitted, data received, parity error, framing error etc. Basically, the event itself just says that something happened, the associated flags indicate what happened, the reason for the event.

Event Listeners

On each event source one or more event listeners can be registered, each event listener is linked to a single thread. The structure establish a many-to-many relationship between event sources and threads.

Event Listeners

Event flags are ORed to the flags field of the event listener, the associates thread can retrieve them using chEvtGetAndClearFlags().

Events Masks

A set of pending events is called an Events Mask and has type eventmask_t, this type has must not be confused with event flags. Each thread has two fields related to events handling:

  • ewmask, is the mask of events the thread is interested in, an AND is performed between this mask and the mask of the pending events.
  • epending, is the mask of the events waiting to be served by the thread.

Operations

There are three fundamental operations involving events.

Registering

The register operation is performed by a thread in order to become a listener of an event source, the association is mediated by an event listener object as follow:

PROCEDURE register(source, listener, events, wflags)
  LET listener.flags = 0
  LET listener.wflags = wflags
  LET listener.events = events
  LET listener.thread = current_thread
  source.listeners = source.listeners + listener
END

Note that there must be a different event listener object for each thread.

Waiting

The wait operation allows a thread to check for pending events or wait for them if none:

FUNCTION wait(events)
  LET current_thread.ewmask = events
  IF current_thread.epending AND current_thread.ewmask = 0
    WAIT
  END
  RETURN current_thread.epending AND current_thread.ewmask
END

ChibiOS/RT implements three variants of the wait primitive called:

  • waitOne. Any of specified events can wakeup the thread, only one is returned.
  • waitAny. Any combination of the specified events can wakeup the thread, they are all returned (OR condition).
  • waitAll. Only the exact combination of the specified events can wakeup the thread. This means that a combination of events must have happened (AND condition).

Broadcasting

The broadcast operation is quite complex:

PROCEDURE broadcast(source, flags)
  FOR EACH source.listeners AS listener
    LET listener.flags = listener.flags OR flags
    IF (listener.flags AND listener.wflags) <> 0
      LET listener.thread.epending = listener.thread.epending OR listener.events
      IF listener.thread.epending AND listener.thread.ewmask <> 0
        WAKEUP listener.thread
      END
    END
  END
END

As you can see the listening thread must be interested in both the broadcasted flags and the specific event in order to be notified.

Simplified Events

There is also another way to use events without recurring to event sources and listeners. A thread can directly signal another thread. In this scenario there is no decoupling between sources and threads, specific threads or ISRs signal specific threads with a mask of event flags.
The targeted object is directly the thread handling the event.

Signaling

PROCEDURE signal(thread, events)
  LET thread.epending = thread.epending OR events
  IF thread.epending AND thread.ewmask <> 0
    WAKEUP thread
  END
END

API

EVENTSOURCE_DECL() Event sources static initializer.
EVENT_MASK() Translates from an event identifier to an event mask.
chEvtObjectInit() Initializes an event source object of type event_source_t.
chEvtRegister() Registers the current thread on an event source by assigning it an event identifier.
chEvtRegisterMask() Registers the current thread on an event source by assigning it a mask of events.
chEvtRegisterMaskWithFlags() Registers the current thread on an event source by assigning it a mask of events and a set of event flags.
chEvtUnregister() Unregisters the current thread from an event source.
chEvtGetAndClearEvents() Returns the events pending for the current thread.
chEvtAddEvents() Adds a mask of events to the current thread.
chEvtSignal() Adds a mask of events to the specified thread.
chEvtSignalI() Adds a mask of events to the specified thread (I-Class variant).
chEvtBroadcast() Performs the broadcast operation on an event source with no flags.
chEvtBroadcastI() Performs the broadcast operation on an event source with no flags (I-Class variant).
chEvtBroadcastFlags() Performs the broadcast operation on an event source and adds the specified flags to the registered event listeners.
chEvtBroadcastFlagsI() Performs the broadcast operation on an event source and adds the specified flags to the registered event listeners (I-Class variant).
chEvtGetAndClearFlags() Returns the event flags pending in the specified event listener.
chEvtGetAndClearFlagsI() Returns the event flags pending in the specified event listener (I-Class variant).
chEvtWaitOne() Waits for exactly one of the specified events.
chEvtWaitAny() Waits for any of the specified events.
chEvtWaitAll() Waits for all the specified events.
chEvtWaitOneTimeout() Waits for exactly one of the specified events with timeout.
chEvtWaitAnyTimeout() Waits for any of the specified events with timeout.
chEvtWaitAllTimeout() Waits for all the specified events with timeout.
chEvtIsListeningI() Verifies if there is at least one listener registered on the event source (I-Class variant).
chEvtDispatch() Calls the functions associated to an events mask.

Examples

Multiple Events

Imagine to have a single thread handling the I/O activity of an application, the thread is registered on multiple event sources and handles all events.

event_source_t network_event_source;
event_source_t serial_event_source;
 
static THD_WORKING_AREA(waListenerThread, 128);
 
static THD_FUNCTION(ListenerThread, arg) {
  event_listener_t network_listener;
  event_listener_t serial_listener;
 
  /* Registering on the network driver as event 0, there are no
     specific flags.*/
  chEvtRegisterMask(&network_event_source,
                    &network_listener,
                    EVENT_MASK(0));
 
  /* Registering on the serial driver as event 1, interested in
     error flags and data-available only, other flags will not wakeup
     the thread.*/
  chEvtRegisterMaskWithFlags(&serial_event_source,
                             &serial_listener,
                             EVENT_MASK(1),
                             SERIAL_FRAMING_ERROR | SERIAL_PARITY_ERROR |
                             SERIAL_DATA_IN);
 
  /* Thread activity.*/
  while (true) {
    /* Waiting for any of the events we're registered on.*/
    eventmask_t evt = chEvtWaitAny(ALL_EVENTS);
 
    /* Serving events.*/
    if (evt & EVENT_MASK(0)) {
      /* Network event, calling the handler, there are no
         flags to handle for the network interface.*/
      network_handler();
    }
    if (evt & EVENT_MASK(1)) {
      /* Event from the serial interface, getting serial
         flags as first thing.*/
      eventflags_t flags = chEvtGetAndClearFlags(&serial_listener);
 
      /* Handling errors first.*/
      if (flags & (SERIAL_FRAMING_ERROR | SERIAL_PARITY_ERROR))
        handle_serial_errors();
      if (flags & SERIAL_DATA_IN)
        handle_serial_data();
    }
  }
}

Direct Signaling

A thread serves events originated in an UART ISR. Note that the thread could be serving events coming from multiple sources, the maximum number of different sources is sizeof(eventmask_t).

#define EVT_UART_RX EVENT_MASK(0)
#define EVT_UART_TX EVENT_MASK(1)
#define EVT_UART_ERR EVENT_MASK(2)
 
/*
 * Pointer to the UART handler thread.
 */
thread_t *uart_thread;
 
/*
 * ISR serving the UART RX FIFO interrupt.
 */
CH_IRQ_HANDLER(UART_RX_IRQ) {
  eventmask_t events = 0;
  uint32_t sr;
 
  /* Resetting status bits.*/
  sr = UART->SR;
  UART->SR = ~sr;
 
  CH_IRQ_PROLOGUE();
 
  /* Events mask initially at zero.*/ 
  events = 0;
 
  /* If there are errors.*/
  if ((UART->SR & UART_SR_ERRORS) != 0)
    events |= EVT_UART_ERR;
 
  /* If there is data available in the UART RX FIFO.*/
  if ((UART->SR & UART_SR_DATA_AVAILABLE) != 0)
    events |= EVT_UART_RX;
 
  /* If there is space in the UART TX FIFO.*/
  if ((UART->SR & UART_SR_TX_EMPTY) != 0) {
    events |= EVT_UART_TX;
 
  /* Segnaling events to the thread, if any.*/
  if (events) {
    chSysLockFromISR();
    chEvtSignalI(uart_thread, events);
    chSysUnlockFromISR();
  }
 
  CH_IRQ_EPILOGUE();
}
 
static THD_WORKING_AREA(waUARTThread, 128);
 
static THD_FUNCTION(UARTThread, arg) {
 
  /* Thread activity.*/
  while (true) {
    /* Waiting for any event.*/
    eventmask_t evt = chEvtWaitAny(ALL_EVENTS);
 
    /* Serving events.*/
    if (evt & EVT_UART_ERR) {
      /* Error event.*/
      error_handler();
    }
    if (evt & EVT_UART_RX) {
      /* Data available event.*/
      rx_handler();
    }
    if (evt & EVT_UART_TX) {
      /* TX ready event.*/
      tx_handler();
    }
  }
}
 
/*
 * Initialization.
 */
void main(void) {
 
  /*
   * System initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  chSysInit();
 
  /* Starting the console thread making it print over the serial
     port.*/
  uart_thread = chThdCreateStatic(waUARTThread, sizeof(waUARTThread),
                                  NORMALPRIO + 1, UARTThread, NULL);
 
  /* Continuing.*/
  ...;
}