How to wake up a Thread from an ISR

Waking up a thread after an hardware event is one of the most common tasks that an RTOS must be able to perform efficiently. In ChibiOS RT and NIL there are several mechanisms that can be used, often each mechanism is best suited in a specific scenario.

Synchronous wake-up

A synchronous wake-up operations operates only on threads that are already waiting for the ISR event in the moment the event happens. The event is not buffered, if there are no waiting threads then nothing happens.

Waking up a specific thread

A common situation is to have to synchronously wake up a specific thread. This can be accomplished without the use of any specific synchronization primitive, it uses very efficient low level APIs, note that you can also optionally send a simple message from the IRQ handler to the thread.

static thread_reference_t trp = NULL;
 
THD_FUNCTION(mythread, arg) {
 
  while (true) {
    msg_t msg;
 
    /* Waiting for the IRQ to happen.*/
    chSysLock();
    activate_interrupt_source();
    msg = chThdSuspendS(&trp);
    chSysUnlock();
 
    /* Perform processing here.*/
    ...
  }
}
 
CH_IRQ_HANDLER(myIRQ) {
  CH_IRQ_PROLOGUE();
 
  /* Wakes up the thread.*/
  chSysLockFromISR();
  chThdResumeI(&trp, (msg_t)0x1337);  /* Resuming the thread with message.*/
  chSysUnlockFromISR().
 
  CH_IRQ_EPILOGUE();
}

Note that before going to sleep the thread calls activate_interrupt_source() from within the critical section, this ensures that the IRQ will happen after the thread goes in suspend.

Waking up a single queued thread

Lets assume you have a queue of waiting threads, you want to wake up the threads one by one in FIFO order, if there are no waiting threads then nothing happens. This can be accomplished using a threads_queue_t object:

_THREADS_QUEUE_DECL(tq);
 
CH_IRQ_HANDLER(myIRQ) {
  CH_IRQ_PROLOGUE();
 
  /* If there is at least one waiting thread then signal it.*/
  chSysLockFromISR();
  chThdDequeueNextI(&tq, (msg_t)0x1337); /* Waking up one with message.*/
  chSysUnlockFromISR().
 
  CH_IRQ_EPILOGUE();
}

Waking up all the queued threads

In this scenario you want to synchronously wake up all the waiting threads, if there are no waiting threads then nothing happens. This can be accomplished using a Semaphore object initialized to zero:

CH_IRQ_HANDLER(myIRQ) {
  CH_IRQ_PROLOGUE();
 
  /* Wakes up all the threads waiting on the semaphore.*/
  chSysLockFromISR();
  chThdDequeueAllI(&tq, (msg_t)0x1337);  /* Waking up all with message.*/
  chSysUnlockFromISR().
 
  CH_IRQ_EPILOGUE();
}

In both the above scenarios the thread would be like this:

THD_FUNCTION(mythread, arg) {
 
  while (true) {
    chSysLock();
    activate_interrupt_source();
    msg_t msg = chThdEnqueueTimeoutS(&tq, TIME_INFINITE);
    chSysUnlock();
 
    /* Perform processing here.*/
    ...
  }
}

Again the thread calls activate_interrupt_source() from within the critical section like in the previous example.

Asynchronous wake-up

An asynchronous wake-up operation can wake up waiting threads like synchronous operations but if the target threads are not immediately ready to serve the event then the event is buffered and the threads will serve it as soon they check for events.

Waking up a specific thread

If you have to asynchronously wake-up a specific thread then a simple event flags can be used.

static thread_t *tp;
 
THD_FUNCTION(mythread, arg) {
 
  tp = chThdGetSelfX();
  while (true) {
    /* Checks if an already IRQ happened else wait.*/
    chEvtWaitAny((eventmask_t)1);
 
    /* Perform processing here.*/
    ...
  }
}
 
CH_IRQ_HANDLER(myIRQ) {
  CH_IRQ_PROLOGUE();
 
  /* Wakes up the thread.*/
  chSysLockFromISR();
  chEvtSignalI(tp, (eventmask_t)1);
  chSysUnlockFromISR().
 
  CH_IRQ_EPILOGUE();
}

Waking up one or more threads

By using event sources it is possible to asynchronously wake up one or more listener threads. The mechanism requires a single initialized event_source_t object, all the threads registered as listeners on the event source will be signaled.

CH_IRQ_HANDLER(myIRQ) {
  CH_IRQ_PROLOGUE();
 
  /* Sets an event flag on all the listening threads.*/
  chSysLockFromISR();
  chEvtBroadcastI(&my_event_source);
  chSysUnlockFromISR().
 
  CH_IRQ_EPILOGUE();
}