CPU Idling
Although normally reserved for the idle thread, in certain special applications, a thread might want to make the CPU idle.
Concepts
Making the CPU idle causes the kernel to pause all operations until an event, normally an interrupt, wakes up the CPU. In a regular system, the idle thread is responsible for this. However, in some constrained systems, it is possible that another thread takes this duty.
Implementation
Making the CPU idle
Making the CPU idle is simple: call the k_cpu_idle() API. The CPU will stop executing instructions until an event occurs. Most likely, the function will be called within a loop. Note that in certain architectures, upon return, k_cpu_idle() unconditionally unmasks interrupts.
static k_sem my_sem;
void my_isr(void *unused)
{
k_sem_give(&my_sem);
}
void main(void)
{
k_sem_init(&my_sem, 0, 1);
/* wait for semaphore from ISR, then do related work */
for (;;) {
/* wait for ISR to trigger work to perform */
if (k_sem_take(&my_sem, K_NO_WAIT) == 0) {
/* ... do processing */
}
/* put CPU to sleep to save power */
k_cpu_idle();
}
}
Making the CPU idle in an atomic fashion
It is possible that there is a need to do some work atomically before making the CPU idle. In such a case, k_cpu_atomic_idle() should be used instead.
In fact, there is a race condition in the previous example: the interrupt could occur between the time the semaphore is taken, finding out it is not available and making the CPU idle again. In some systems, this can cause the CPU to idle until another interrupt occurs, which might be never, thus hanging the system completely. To prevent this, k_cpu_atomic_idle() should have been used, like in this example.
static k_sem my_sem;
void my_isr(void *unused)
{
k_sem_give(&my_sem);
}
void main(void)
{
k_sem_init(&my_sem, 0, 1);
for (;;) {
unsigned int key = irq_lock();
/*
* Wait for semaphore from ISR; if acquired, do related work, then
* go to next loop iteration (the semaphore might have been given
* again); else, make the CPU idle.
*/
if (k_sem_take(&my_sem, K_NO_WAIT) == 0) {
irq_unlock(key);
/* ... do processing */
} else {
/* put CPU to sleep to save power */
k_cpu_atomic_idle(key);
}
}
}
Suggested Uses
Use k_cpu_atomic_idle() when a thread has to do some real work in addition to idling the CPU to wait for an event. See example above.
Use k_cpu_idle() only when a thread is only responsible for idling the CPU, i.e. not doing any real work, like in this example below.
void main(void)
{
/* ... do some system/application initialization */
/* thread is only used for CPU idling from this point on */
for (;;) {
k_cpu_idle();
}
}
Note
Do not use these APIs unless absolutely necessary. In a normal system, the idle thread takes care of power management, including CPU idling.
API Reference
- group cpu_idle_apis
Functions
-
static inline void k_cpu_idle(void)
Make the CPU idle.
This function makes the CPU idle until an event wakes it up.
In a regular system, the idle thread should be the only thread responsible for making the CPU idle and triggering any type of power management. However, in some more constrained systems, such as a single-threaded system, the only thread would be responsible for this if needed.
Note
In some architectures, before returning, the function unmasks interrupts unconditionally.
-
static inline void k_cpu_atomic_idle(unsigned int key)
Make the CPU idle in an atomic fashion.
Similar to k_cpu_idle(), but must be called with interrupts locked.
Enabling interrupts and entering a low-power mode will be atomic, i.e. there will be no period of time where interrupts are enabled before the processor enters a low-power mode.
After waking up from the low-power mode, the interrupt lockout state will be restored as if by irq_unlock(key).
- Parameters
key – Interrupt locking key obtained from irq_lock().
-
static inline void k_cpu_idle(void)