How to wake up a thread from an interrupt handler

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 there are several mechanisms that can be used, often each mechanism is best suited in a specific scenario.

Synchronous wakeup

A synchronous wakeup operations operates only on threads that are already waiting for the ISR event in the moment the event happens. The event is no way buffered if there are no waiting threads.

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 the very efficient low level scheduler APIs, note that you can also optionally send a simple message from the IRQ handler to the thread.

static Thread *tp = NULL;
 
void mythread(void *p) {
 
  while (TRUE) {
    msg_t msg;
 
    /* Waiting for the IRQ to happen.*/
    chSysLock();
    tp = chThdSelf();
    chSchGoSleepS(THD_STATE_SUSPENDED);
    msg = chThdSelf()->p_u.rdymsg;  /* Retrieving the message, optional.*/
    chSysUnlock();
    /* Perform processing here.*/
  }
}
 
CH_IRQ_HANDLER(myIRQ) {
  CH_IRQ_PROLOGUE();
 
  /* Wakes up the thread.*/
  chSysLockFromIsr();
  if (tp != NULL) {
    tp->p_u.rdymsg = (msg_t)55;     /* Sending the message, optional.*/
    chSchReadyI(tp);
    tp = NULL;
  }
  chSysUnlockFromIsr().
 
  CH_IRQ_EPILOGUE();
}

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 Semaphore object initialized to zero:

CH_IRQ_HANDLER(myIRQ) {
  CH_IRQ_PROLOGUE();
 
  /* If there is at least one waiting thread then signal it.*/
  chSysLockFromIsr();
  if (chSemGetCounterI(&mysem) < 0)
    chSemSignalI(&mysem);
  chSysUnlockFromIsr().
 
  CH_IRQ_EPILOGUE();
}

Waking up all the waiting 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();
  chSemResetI(&mysem);
  chSysUnlockFromIsr().
 
  CH_IRQ_EPILOGUE();
}

The thread would be like this:

msg_t mythread(void *p) {
  
  while (TRUE) {
    chSemWait(&mysem);
    /* Do something.*/
  }
}

Asynchronous wakeup

An asynchronous wakeup 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 *tp;
 
void mythread(void *p) {
 
  tp = chThdSelf();
  while (TRUE) {
    /* Checks if an 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 EventSource object, all the threads registered as listeners on the event source will be broadcasted.

CH_IRQ_HANDLER(myIRQ) {
  CH_IRQ_PROLOGUE();
 
  /* Pends an event flag on all the listening threads.*/
  chSysLockFromIsr();
  chEvtBroadcastI(&my_event_source);
  chSysUnlockFromIsr().
 
  CH_IRQ_EPILOGUE();
}
 
chibios/howtos/wakeup.txt · Last modified: 2014/05/15 17:03 by giovanni
 
Except where otherwise noted, content on this wiki is licensed under the following license:GNU Free Documentation License 1.3