分类: Android平台
2013-07-26 11:05:38
Power managementEdit
Abstract
Android Power Management is rely on basic function that Linux kernel provied, and android also has added features like wakelock and early suspend. We will introduce standard linux power management first, then how android power manage framework works with linux kernel. Also we will have a step by step introduction of how to add power management support in android.
Power Management in Linux Kernel
Introduction of Linux Power Manager
Modern Linux distribution supports suspend or hibernate functionality. In the Linux kernel, Hibernate or suspend-to-disk, suspend-to-ram, suspend-to-file are supported. Suspend have 3 major part: Freezing process and tasks, Call every driver's suspend callback and Suspend CPU. Freezing process is 100% transparent to kernel and user processes, it stops all task and process, and when resume, they will continue execute as if not stop ever. User space processes and kernel space tasks will never know this stop.
The power management interface for Linux kernel provided to user is sys fs. User can read/write sys fs file: /sys/power/state to control and get kernel power managment service. such as:
echo standby > /sys/power/state -- to let system going to suspend.
cat /sys/power/state -- to get how many PM method you kernel supported.
How Power Manager Works In Linux Kernel
The Linux power management code is in kernel/power. Let's take a look how normal linux suspend works in the code:
1 The userspace interface /sys/power/state is state_store() function in main.c, when echo "standby" or "mem", which is defined by const char * const pm_state[] to /sys/power/state, It will check if system support the feature and go to static int enter_state(suspend_state_t state):
2 in static int enter_state(suspend_state_t state), it will check if the requested state is valid and call sys_sync to sync the filesystem, this will spend some time depends the filesystems in use. Below is the source 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;
}
Going to suspend_prepare(), this func will alloc a console for suspend, running suspend notifiers, disable user mode helper, and call suspend_freeze_processes() freeze all process, it will make all process save current state, in the freeze stage, maybe some task/user space process will refuze to going to freezing,it will abort and unfreezing all precess.
/**
* 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;
}
For now, all the other process(process/workqueue/kthread) is stoped, they may have locked semaphore, if you waiting for them in driver's suspend function, it will a dead lock. And then, kernel will free some memory for later use. Finally, it will call suspend_devices_and_enter() to suspend all devices, in this function, first will call suspend_ops->begin() if this machine have this function, device_suspend() in driver/base/power/main.c will be called, this function will call dpm_suspend() to all all device list and their suspend() callback. After suspend devices, it will call the suspend_ops->prepare() to let machine do some machine related prepare job(could be empty on some machine), in multiple cpu case, it will disable nonboot cpus to avoid race conditions, so , after that, it will only one cpu will running. suspend_ops is a machine related pm op, normally it registed by arch/xxx/mach-xxx/pm.c And then, is suspend_enter() will be called, here will disable arch irqs will suspend, call device_power_down(), this message will call each of suspend_late() callback, thi will be the last call back before system hold, and suspend all system devices, I guess it means, all devices under /sys/devices/system/*, and then it will call suspend_pos->enter() to let system going to a power save mode, system will stop and the code executing stop 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;
}
If the system wake up by interrupt or other event, the code executing will be continue. The first thing system resume is resume the devices under /sys/devices/system/, and enable irq, and then, it will enable nonboot cpus, and call suspend_ops->finish() to let machine know it will start resume, suspend_devices_and_enter() function later will will call every device 's resume() fucntion to resume devices, resume the console, and finally, call the suspend_ops->end(). Let's return to enter_state() function, after suspend_devices_and_enter() returns, the devices is running, but user space process and task is still freezed, enter_state will later call suspend_finish(), it will thaw the processes and enable user mode helper, and notify all pm they are exit from a suspend stage, and resume the console. This is a stardard linux suspend and resume sequence.
Suspend in Android Kernel
Android patched linux kernel, let first introduct serval new feather android imported, related code is:
· linux_source/kernel/power/main.c
· linux_source/kernel/power/earlysuspend.c
· linux_source/kernel/power/wakelock.c
Introduction of Android Kernel Suspend
Early Suspend
Early suspend is a mechanism that android introduced into linux kernel. This state is btween really suspend, and trun off screen. After Screen is off, several device such as LCD backlight, gsensor, touchscreen will stop for battery life and functional requirement.
Late Resume
Late resume is a mechinism pairs to early suspend, executed after the kernel and system resume finished. It will resume the devices suspended during early suspend.
Wake Lock
Wake lock acts as a core member in android power management system. wake lock is a lock can be hold by kernel space ,system servers and applications with or without timeout. In an android patched linux kernel (referenced as android kernel below) will timing how many and how long the lock have. If there isn't any of wake lock prevent suspend(WAKE_LOCK_SUSPEND), android kernel will call linux suspend (pm_suspend()) to let entire system going to suspend.
How Power Management works in Android Kernel
Same as standard Linux, when user write "mem"/"stanby" to /sys/power/state the state_store() will called. And then will going to request_suspend_state() instead of enter_state(), this function will check the state, if the request is suspend it will queue the early_suspend_work -> early_suspend(),
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);
}
In early_suspend(): It will first check was the state still suspend (in case the suspend request was canceled during the time), if abort, this work will quit. If not, this func will call the all of registered early suspend handlers, and call suspend() of these handlers. And then, sync file system, and most important, give up a main_wake_lock, this wake lock is used by wakelock self and early suspend. This wake lock is not a timeout wake lock, so, if this lock is holded, wake lock will not going to suspend even these was none of wake lock actived. During this time, the system suspend was not called. Because of early suspend give up the main_wake_lock, so the wake lock can decide if going to suspend the system.
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);
}
After all the kernel resume is finished, the user space process and service is running, the wake up of system is invoked by an power key event.
When system waked by a key event, such as a power key, or menu key, these key event will send to WindowManager, and it will deal with it, if the key is not the key can wake up system, such as return key/home key, the WindowManager will drop the wake lock to let system going to suspend again. if the key is a wake key, the WindowManager will RPC PowerManagerSerivce interface to execute late resume. PowerManagerSerivce also will write "on" to interface to let kernel execute late resume. Late Resume will call the resume func in list of early suspend devices.
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);
}
Let's see how the wake lock mechinism run, we will focus on file wakelock.c. wake lock have to state, lock or unlock. The Lock have two method:
3 Unlimited Lock:This type of lock will never unlock until some one call unlock
4 Wake Lock with Timeout:This type of lock is alloc with a timeout, is the time expired, this lock will automatic unlock.
Also have two type of lock:
5 WAKE_LOCK_SUSPEND:This type of Lock will prevent system going to suspend.
6 WAKE_LOCK_IDLE:This type of Lock not prevent system going to suspend, not a lock can make system wake, I can't figure out why this lock exist.In wake lock functions, there was 3 enter pointer can call the suspend() workqueue:
7 In wake_unlock(), if there was none of wake lock after unlock, the suspend started.
8 after the timeout timer expired, the callback of timer will be called, in this function, it will check if there no of wake lock, system goto suspend.
9 In wake_lock(), if add lock success, it will check if there was none of wake lock, if none of wake lock, it will going to suspend. I think the way check here is unnessary at all, the better way is let wake_lock() wake_unlock() to be atomic, since this check add here also have chance missing the unlock.
If the wake lock call the suspend workqueue, the suspend() will be called, this function check wake lock,sysc file system, and then call the pm_suspend()->enter_state() to going standard linux suspend sequence.
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);
}
}
The pm_suspend() will call the enter_state() to going to a suspend() state, but it's not 100% same as standard kernel suspend sequence:
· When freezing process, android will check if there was any of wakelock, if have, the suspend sequence will be interrupted.
· In suspend_late callback, this callback will have a final check of wake lock, if some driver or freezed have the wake lock, it will return an error, this will make system going to resume.
Power Management in Android Framework
Introduction of Android Power Management[by Steve Guo]
Overview:
The above picture shows the overall architecture design of Android power management module. Android implements a very simple power management mechanism. Currently it only supports set screen on/off, screen backlight on/off, keyboard backlight on/off, button backlight on/off and adjust screen brightness. It does not support Sleep or Standby mode to fully use CPU’s capability.
The power management module has three channels to receive input: RPC call, Batter state change event and Power Setting change event. It communicated with other modules through either broadcasting intent or directly API call. The module also provide reboot and shutdown service. When battery is lower than thredshold, it will automatically shutdown the device.
The module will automatically set screen dim and off according to whether any user activity happens or not. The full state machine is shown as follows:
Detail:
PowerManagerService.java is the core service. It calls Power.java to do the real work.
PowerManager.java is the proxy to RPC call PowerManagerService.java.
Power.java communicates with the low level through JNI.
android_os_Power.cpp is the JNI native implementation for Power.java. It calls Power.c to do the real work.
Power.c controls the power device driver through read/write the following sys files.
"/sys/android_power/acquire_partial_wake_lock",
"/sys/android_power/acquire_full_wake_lock",
"/sys/android_power/release_wake_lock",
"/sys/android_power/request_state"
"/sys/android_power/auto_off_timeout",
"/sys/class/leds/lcd-backlight/brightness",
"/sys/class/leds/button-backlight/brightness",
"/sys/class/leds/keyboard-backlight/brightness"
BatteryService.java registers itself as a UEvent observer for the path “/sys/class/power_supply”. If anything is changed in this path, it gets current state through JNI and then broadcasts ACTION_BATTERY_CHANGED intent.
com_android_server_BatteryService.cpp is the JNI native implementation for BatteryService.java. It gets current battery state through reading from the following files:
"/sys/class/power_supply/ac/online"
"/sys/class/power_supply/usb/online"
"/sys/class/power_supply/battery/status"
"/sys/class/power_supply/battery/health"
"/sys/class/power_supply/battery/present"
"/sys/class/power_supply/battery/capacity"
"/sys/class/power_supply/battery/batt_vol"
"/sys/class/power_supply/battery/batt_temp"
"/sys/class/power_supply/battery/technology"
How to use:
To call power module in app, the following is the sample code:
PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE, TAG);
wl.acquire();
// ...
wl.release();
Interface to Kernel Power Management:
PowerManagerService.java is in charge for power management in framework. goToSleep() is the API to suspend system, the call stack is:
goToSleep()->goToSleepLocked()->setPowerState()->setScreenStateLocked()->Power.setScreenState(on)->set_screen_state();
setScreenState() in android_os_Power.cpp:
static int setScreenState(JNIEnv *env, jobject clazz, jboolean on)
{
return set_screen_state(on);
}
set_screen_state() in power.c:
int set_screen_state(int on)
{
QEMU_FALLBACK(set_screen_state(on));
LOGI("*** set_screen_state %d", on);
initialize_fds();
//LOGI("go_to_sleep eventTime=%lld now=%lld g_error=%s\n", eventTime,
// systemTime(), strerror(g_error));
if (g_error) return g_error;
char buf[32];
int len;
if(on)
len = sprintf(buf, on_state);
else
len = sprintf(buf, off_state);
len = write(g_fds[REQUEST_STATE], buf, len);
if(len < 0) {
LOGE("Failed setting last user activity: g_error=%d\n", g_error);
}
return 0;
}
g_fds[REQUEST_STATE] = open("/sys/power/state", O_RDWR);
on_state is "wake"; off_state is "standby";
so write "standby"/"wake" to g_fds[REQUEST_STATE], will write to sys fs "/sys/power/statem" to invoke kernel suspend/wakeup.
Guide to add power management in Linux kernel
1) write pm.c in arch/arm/arch-xxx to register suspend_ops, register early_suspend/suspend code to trun off/on clock/pll/power gates for system suspend
2) write sleep.S to power gate CPU, turn DDR to self refresh mode
3) to use pm kernel, enable power management, wake lock, suspend, early suspend, sys fs interface
4) add power key with interrupr support in input driver
5) add suspend/resume early suspend/late resume for drivers
6) add GPIO, EXGPIO and pinmux handling code in bsp for suspend, for example:
#define MAX_GPIO 9
static gpio_data_t gpio_data[MAX_GPIO] = {
// ----------------------------------- power ctrl ---------------------------------
{"GPIOC_3 -- AVDD_EN", GPIOC_bank_bit0_26(3), GPIOC_bit_bit0_26(3), GPIO_OUTPUT_MODE, 1, 1},
{"GPIOA_7 -- BL_PWM", GPIOA_bank_bit0_14(7), GPIOA_bit_bit0_14(7), GPIO_OUTPUT_MODE, 1, 1},
{"GPIOA_6 -- VCCx2_EN", GPIOA_bank_bit0_14(6), GPIOA_bit_bit0_14(6), GPIO_OUTPUT_MODE, 1, 1},
// ----------------------------------- i2s ---------------------------------
{"TEST_N -- I2S_DOUT", GPIOJTAG_bank_bit(16), GPIOJTAG_bit_bit16(16), GPIO_OUTPUT_MODE, 1, 1},
// ----------------------------------- wifi&bt ---------------------------------
{"GPIOD_12 -- WL_RST_N", GPIOD_bank_bit2_24(12), GPIOD_bit_bit2_24(12), GPIO_OUTPUT_MODE, 1, 1},
{"GPIOD_14 -- BT/GPS_RST_N",GPIOD_bank_bit2_24(14), GPIOD_bit_bit2_24(14), GPIO_OUTPUT_MODE, 1, 1},
{"GPIOD_18 -- UART_CTS_N", GPIOD_bank_bit2_24(18), GPIOD_bit_bit2_24(18), GPIO_OUTPUT_MODE, 1, 1},
{"GPIOD_21 -- BT/GPS", GPIOD_bank_bit2_24(21), GPIOD_bit_bit2_24(21), GPIO_OUTPUT_MODE, 1, 1},
// ----------------------------------- lcd ---------------------------------
{"GPIOC_12 -- LCD_U/D", GPIOC_bank_bit0_26(12), GPIOC_bit_bit0_26(12), GPIO_OUTPUT_MODE, 1, 1},
{"GPIOA_3 -- LCD_PWR_EN", GPIOA_bank_bit0_14(3), GPIOA_bit_bit0_14(3), GPIO_OUTPUT_MODE, 1, 1},
};
#define MAX_PINMUX 12
pinmux_data_t pinmux_data[MAX_PINMUX] = {
{"HDMI", 0, (1<<2)|(1<<1)|(1<<0), 1},
{"TCON", 0, (1<<14)|(1<<11), 1},
{"I2S_OUT", 0, (1<<18), 1},
{"I2S_CLK", 1, (1<<19)|(1<<15)|(1<<11), 1},
{"SPI", 1, (1<<29)|(1<<27)|(1<<25)|(1<<23), 1},
{"I2C", 2, (1<<5)|(1<<2), 1},
{"SD", 2, (1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<8), 1},
{"PWM", 2, (1<<31), 1},
{"UART_A", 3, (1<<24)|(1<23), 0},
{"RGB", 4, (1<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0), 1},
{"UART_B", 5, (1<<24)|(1<23), 0},
{"REMOTE", 5, (1<<31), 1},
};
7) and pm device into bsp board-xxx.c and write pm parameters structure
struct meson_pm_config {
void __iomem *pctl_reg_base; //
void __iomem *mmc_reg_base;
void __iomem *hiu_reg_base;
void __iomem *power_key;
unsigned ddr_clk;
void __iomem *ddr_reg_backup;
unsigned core_voltage_adjust;
int sleepcount;
void (*set_vccx2)(int power_on);
void (*set_exgpio_early_suspend)(int power_on);
};
static struct meson_pm_config aml_pm_pdata = {
.pctl_reg_base = IO_APB_BUS_BASE,
.mmc_reg_base = APB_REG_ADDR(0x1000),
.hiu_reg_base = CBUS_REG_ADDR(0x1000),
.power_key = CBUS_REG_ADDR(RTC_ADDR1),
.ddr_clk = 0x00110820, // used for ddk clock when suspend, now turn off, so not used
.sleepcount = 128, // not used now
.set_vccx2 = set_vccx2, // callback function in full suspend for customized power saving code(GPIO/PINMUX) in bsp
.core_voltage_adjust = 5, // core voltage adjust parameter, 0 for not adjust, range 0~15, larger the lower
};
8. use xtal or pll for clk81 in early suspend:
In case device read speed is not fast enough in early suspend, #undef EARLY_SUSPEND_USE_XTAL will keep clk81 using PLL.
// To add details
Enable power management in Android Framework
1) set nopm = false in system.prop