Introduction
Sleep/Wakeup is a very important part in embedded Linux. The embedded device enters sleep state as much as possible to prolong the battery life. this article details how sleep/Wakeup works in Linux, and how to associate this part with the Linux mechanism in Android.
Internationalization
- English version: Link
- Chinese version: Link
Author: zhangjiejing <kzjeef # gmail.com> date: 2010-04-07, http://www.thinksrc.com
Version Information
- Linux Kernel: v2.6.28
- Android: V2.0
Introduction to sleep (suspend)
In Linux, sleep mainly involves three steps:
- Freeze user-State processes and kernel-state tasks
- Call the suspend callback function of the registered device.
- The order is the registration order.
- Sleep the core device and sleep the CPU into a sleep frozen process. The kernel sets the state of all processes in the process list to stop and saves the context of all processes. when these processes are restored, they do not know that they have been frozen, but simply continue to execute. how can we break Linux into sleep? You can read and write the Sys File/sys/power/State to enable the control system to sleep. For example
# echo standby > /sys/power/state
The command system goes to sleep. You can also use
# cat /sys/power/state
Which sleep modes are supported by the kernel.
Files related to the Linux suspend process:
You can access the Linux kernel website to obtain the source code. The file path is as follows:
- Linux_soruce/kernel/power/Main. c
- Linux_source/kernel/ARCH/XXX/Mach-xxx/PM. c
- Linux_source/driver/base/power/Main. c
Next let's take a detailed look at how Linux sleep/wakes up. Let's going to see how these happens.
The user's read/write operations on/sys/power/State call main. state_store () in C. You can write strings defined in const char * const pm_state [], such as "mem", "standby ".
Then state_store () will call enter_state (). It will first check some status parameters and then synchronize the file system. The following is the code:
/** * enter_state - Do common work of entering low-power state. * @state: pm_state structure for state we're entering. * * Make sure we're the only ones trying to enter a sleep state. Fail * if someone has beat us to it, since we don't want anything weird to * happen when we wake up. * Then, do the setup for suspend, enter the state, and cleaup (after * we've woken up). */static int enter_state(suspend_state_t state){ int error; if (!valid_state(state)) return -ENODEV; if (!mutex_trylock(&pm_mutex)) return -EBUSY; printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); printk("done./n"); pr_debug("PM: Preparing system for %s sleep/n", pm_states[state]); error = suspend_prepare(); if (error) goto Unlock; if (suspend_test(TEST_FREEZER)) goto Finish; pr_debug("PM: Entering %s sleep/n", pm_states[state]); error = suspend_devices_and_enter(state); Finish: pr_debug("PM: Finishing wakeup./n"); suspend_finish(); Unlock: mutex_unlock(&pm_mutex); return error;}
Prepare and freeze Processes
After entering suspend_prepare (), it will allocate a virtual terminal to suspend to output information, broadcast a system to enter suspend's notify y, and disable the user-mode helper process, then call suspend_freeze_processes () to freeze all processes. The current state of all processes will be saved here. Some processes may refuse to enter the frozen state. When such a process exists, this function will stop freezing processes and restore all frozen processes.
/** * suspend_prepare - Do prep work before entering low-power state. * * This is common code that is called for each state that we're entering. * Run suspend notifiers, allocate a console and stop all processes. */static int suspend_prepare(void){ int error; unsigned int free_pages; if (!suspend_ops || !suspend_ops->enter) return -EPERM; pm_prepare_console(); error = pm_notifier_call_chain(PM_SUSPEND_PREPARE); if (error) goto Finish; error = usermodehelper_disable(); if (error) goto Finish; if (suspend_freeze_processes()) { error = -EAGAIN; goto Thaw; } free_pages = global_page_state(NR_FREE_PAGES); if (free_pages < FREE_PAGE_NUMBER) { pr_debug("PM: free some memory/n"); shrink_all_memory(FREE_PAGE_NUMBER - free_pages); if (nr_free_pages() < FREE_PAGE_NUMBER) { error = -ENOMEM; printk(KERN_ERR "PM: No enough memory/n"); } } if (!error) return 0; Thaw: suspend_thaw_processes(); usermodehelper_enable(); Finish: pm_notifier_call_chain(PM_POST_SUSPEND); pm_restore_console(); return error;}
Sleep peripherals
Now, all processes (including workqueue/kthread) have stopped, and kernel-state characters may hold some semaphores when they are stopped, so if you unlock this signal in the peripherals at this time, there may be deadlocks, so you should be very careful when making the lock/unlock lock in the suspend () function of the peripherals, we recommend that you do not wait for the lock in suspend () during design. in addition, some logs cannot be output during suspend, so once a problem occurs, debugging is very difficult.
Then the kernel will try to release some memory here.
At last, we will call suspend_devices_and_enter () to sleep all peripherals. In this function, if the platform registers suspend_pos (usually defined and registered in the board-level definition ), here we will call suspend_ops-> begin (), and then driver/base/power/main. in C, device_suspend ()-> dpm_suspend () will be called, and they will call the suspend () callback of the driver in sequence to sleep all devices.
When all devices sleep, suspend_ops-> prepare () will be called. This function usually makes some preparations to let the machine sleep. in Linux, non-startup CPUs in multi-core CPUs will be turned off. You can see through comments that the other CPUs will not cause race condion, and then only one CPU will be running.
Suspend_ops is an on-board power management operation, which is usually registered in the file ARCH/XXX/Mach-xxx/PM. C.
Next, suspend_enter () will be called. This function will disable arch IRQ and call device_power_down (). It will call the suspend_late () function, this function is the final function called by the system to sleep. It usually performs the final check in this function. if the check is correct, sleep all the system devices and buses and call suspend_pos-> enter () to enable the CPU to power down. at this time, it is sleep. code execution stops here.
/** * suspend_devices_and_enter - suspend devices and enter the desired system * sleep state. * @state: state to enter */int suspend_devices_and_enter(suspend_state_t state){ int error, ftrace_save; if (!suspend_ops) return -ENOSYS; if (suspend_ops->begin) { error = suspend_ops->begin(state); if (error) goto Close; } suspend_console(); ftrace_save = __ftrace_enabled_save(); suspend_test_start(); error = device_suspend(PMSG_SUSPEND); if (error) { printk(KERN_ERR "PM: Some devices failed to suspend/n"); goto Recover_platform; } suspend_test_finish("suspend devices"); if (suspend_test(TEST_DEVICES)) goto Recover_platform; if (suspend_ops->prepare) { error = suspend_ops->prepare(); if (error) goto Resume_devices; } if (suspend_test(TEST_PLATFORM)) goto Finish; error = disable_nonboot_cpus(); if (!error && !suspend_test(TEST_CPUS)) suspend_enter(state); enable_nonboot_cpus(); Finish: if (suspend_ops->finish) suspend_ops->finish(); Resume_devices: suspend_test_start(); device_resume(PMSG_RESUME); suspend_test_finish("resume devices"); __ftrace_enabled_restore(ftrace_save); resume_console(); Close: if (suspend_ops->end) suspend_ops->end(); return error; Recover_platform: if (suspend_ops->recover) suspend_ops->recover(); goto Resume_devices;}
Resume
If the system is interrupted or other events are awakened during sleep, the following code starts to run. The wake-up sequence is the opposite to that of sleep, therefore, the system device and bus will first wake up, enable the system to be disconnected, enable non-startup CPU to be stopped during sleep, and call suspend_ops-> finish (), and in suspend_devices_and_enter () the function will also wake up each device, enable the virtual terminal, and finally call suspend_ops-> end ().
In the return to the enter_state () function, when suspend_devices_and_enter () returns, the peripherals are awakened, but the processes and tasks are still frozen. suspend_finish () is called here () to unfreeze these processes and tasks, and send a running y to indicate that the system has exited from the suspend status and wakened the terminal.
By now, all sleep and wakeup have been completed, and the system continues to run.
Android sleep (suspend)
In a kernel that has been installed with the android patch, the state_store () function goes through another path and enters request_suspend_state (). The file is located in earlysuspend. c. these functions are added to the Android system. earlysuspend and late resume will be introduced later.
Involved files:
- Linux_source/kernel/power/Main. c
- Linux_source/kernel/power/earlysuspend. c
- Linux_source/kernel/power/wakelock. c
Features early suspend
Early suspend is a mechanism introduced by Android. This mechanism is highly controversial in the upstream and will not be commented here. this mechanism is used to turn off the display. At this time, some display-related devices, such as LCD backlights, such as Gravity Sensors and touch screens, are switched off, however, the system may still be running (at this time, there is also a wake lock) for task processing, such as scanning files on the SD card. in embedded devices, backlight is a huge power consumption, so android will add such a mechanism.
Late resume
Late resume is a mechanism supporting suspend. It is used to wake up the device that is sleeping during early suspend.
Wake lock
Wake lock plays a core role in the android power management system. wake lock is a lock mechanism. As long as someone holds this lock, the system will not be able to sleep and can be obtained by user State programs and kernels. the lock can be time-out or not time-out. The lock will be automatically unlocked after the time expires. if there is no lock or timeout, the kernel will start the sleep mechanism to enter sleep.
Android suspend
When the user writes mem or standby to/sys/power/State, state_store () will be called, and Android will call request_suspend_state () here () standard Linux will enter the enter_state () function here. if the request is sleep, the workqueue early_suspend will be called and enters the early_suspend state.
void request_suspend_state(suspend_state_t new_state){ unsigned long irqflags; int old_sleep; spin_lock_irqsave(&state_lock, irqflags); old_sleep = state & SUSPEND_REQUESTED; if (debug_mask & DEBUG_USER_STATE) { struct timespec ts; struct rtc_time tm; getnstimeofday(&ts); rtc_time_to_tm(ts.tv_sec, &tm); pr_info("request_suspend_state: %s (%d->%d) at %lld " "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)/n", new_state != PM_SUSPEND_ON ? "sleep" : "wakeup", requested_suspend_state, new_state, ktime_to_ns(ktime_get()), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); } if (!old_sleep && new_state != PM_SUSPEND_ON) { state |= SUSPEND_REQUESTED; queue_work(suspend_work_queue, &early_suspend_work); } else if (old_sleep && new_state == PM_SUSPEND_ON) { state &= ~SUSPEND_REQUESTED; wake_lock(&main_wake_lock); queue_work(suspend_work_queue, &late_resume_work); } requested_suspend_state = new_state; spin_unlock_irqrestore(&state_lock, irqflags);}
Early suspend
In the early_suspend () function, first check whether the current request status is suspend to prevent suspend requests from being canceled at this time (because the user process is still running at this time ), if you need to exit, simply quit. if no, this function will call a series of callbacks registered in early suspend once, synchronize the file system, and then discard main_wake_lock. The wake lock is a lock without timeout, if the lock is not released, the system will not be able to sleep.
static void early_suspend(struct work_struct *work){ struct early_suspend *pos; unsigned long irqflags; int abort = 0; mutex_lock(&early_suspend_lock); spin_lock_irqsave(&state_lock, irqflags); if (state == SUSPEND_REQUESTED) state |= SUSPENDED; else abort = 1; spin_unlock_irqrestore(&state_lock, irqflags); if (abort) { if (debug_mask & DEBUG_SUSPEND) pr_info("early_suspend: abort, state %d/n", state); mutex_unlock(&early_suspend_lock); goto abort; } if (debug_mask & DEBUG_SUSPEND) pr_info("early_suspend: call handlers/n"); list_for_each_entry(pos, &early_suspend_handlers, link) { if (pos->suspend != NULL) pos->suspend(pos); } mutex_unlock(&early_suspend_lock); if (debug_mask & DEBUG_SUSPEND) pr_info("early_suspend: sync/n"); sys_sync();abort: spin_lock_irqsave(&state_lock, irqflags); if (state == SUSPEND_REQUESTED_AND_SUSPENDED) wake_unlock(&main_wake_lock); spin_unlock_irqrestore(&state_lock, irqflags);}
Late resume
After all the wake-up operations are completed, user processes have started to run. Wake-up is usually due to the following reasons:
For incoming calls, the modem sends a command to rild to notify windowmanager of incoming call response, in this way, the device that remotely calls powermanagerservice to write "on" to/sys/power/State to execute late resume, such as lighting the screen.
- The user presses the user key event and sends it to windowmanager. windowmanager processes these key events. The buttons are divided into several situations. If the case is not a wake-up key, the system can be awakened) windowmanager will take the initiative to discard wakelock to enable the system to sleep again. If the key is the wake-up key, windowmanger will call the interface in powermanagerservice to execute late resume.
- Late resume will wake up the device that previously called early suspend.
static void late_resume(struct work_struct *work){ struct early_suspend *pos; unsigned long irqflags; int abort = 0; mutex_lock(&early_suspend_lock); spin_lock_irqsave(&state_lock, irqflags); if (state == SUSPENDED) state &= ~SUSPENDED; else abort = 1; spin_unlock_irqrestore(&state_lock, irqflags); if (abort) { if (debug_mask & DEBUG_SUSPEND) pr_info("late_resume: abort, state %d/n", state); goto abort; } if (debug_mask & DEBUG_SUSPEND) pr_info("late_resume: call handlers/n"); list_for_each_entry_reverse(pos, &early_suspend_handlers, link) if (pos->resume != NULL) pos->resume(pos); if (debug_mask & DEBUG_SUSPEND) pr_info("late_resume: done/n");abort: mutex_unlock(&early_suspend_lock);}
Wake lock
Let's take a look at how the wake lock mechanism runs and works, and focus on the wakelock. c file.
A wake lock has two statuses: locking and unlocking. One is a permanent lock. Such a lock will not be unlocked unless it is displayed, therefore, the use of such locks is very careful. the second is the timeout lock, which will lock the system for a period of time. If the time passes, the lock will be automatically lifted.
There are two types of locks:
- The locks such as wake_lock_suspend prevent the system from sleep.
- The locks such as wake_lock_idle do not affect the sleep of the system. I am not very clear about the function.
In the wake lock, there are three places for the system to directly start suspend (), respectively:
- In wake_unlock (), if no other wake lock is found after unlocking, sleep begins.
- After the timer has reached the time, the timer callback function will check whether there are other wake locks. If not, let the system go to sleep.
- In wake_lock (), after a wake lock is locked, it will check again whether there is a lock. I think the check here is unnecessary, A better way is to make the lock operation atomic, rather than tedious checks. this check may also be missed.
Suspend
After wake_lock runs suspend. c's suspend () function will be called. This function first synchronizes the file system, then calls pm_suspend (request_suspend_state), and then pm_suspend () will call enter_state () to enter the Linux sleep process ..
static void suspend(struct work_struct *work){ int ret; int entry_event_num; if (has_wake_lock(WAKE_LOCK_SUSPEND)) { if (debug_mask & DEBUG_SUSPEND) pr_info("suspend: abort suspend/n"); return; } entry_event_num = current_event_num; sys_sync(); if (debug_mask & DEBUG_SUSPEND) pr_info("suspend: enter suspend/n"); ret = pm_suspend(requested_suspend_state); if (current_event_num == entry_event_num) { wake_lock_timeout(&unknown_wakeup, HZ / 2); }}
Differences between Android and standard Linux sleep
Although pm_suspend () will call enter_state () to enter the standard Linux sleep process, there are some differences:
- When a frozen process is started, Android will first check whether there is a wake lock. If not, the processes will be stopped, because some people may apply for a wake lock during the suspend and freeze processes, if so, the frozen process will be interrupted.
- In suspend_late (), the system will check whether there is a wake lock at the last time. This may be caused by a process that quickly applies for the wake lock and quickly releases the lock. In this case, here an error is returned, and the entire suspend will be abandoned. if pm_suspend () succeeds, you can add "no_console_suspend" to the kernel cmd to view log output in the suspend and resume processes.