Linux, Android电源管理:wakelock,autosleep

Linux内核中wakelock的实现

//kernel/kernel/power/Makefile
ccflags-$(CONFIG_PM_DEBUG) := -DDEBUG

obj-y               += qos.o
obj-$(CONFIG_PM)       += main.o
obj-$(CONFIG_VT_CONSOLE_SLEEP) += console.o //??
obj-$(CONFIG_FREEZER)      += process.o
obj-$(CONFIG_SUSPEND)      += suspend.o
obj-$(CONFIG_PM_TEST_SUSPEND)  += suspend_test.o
obj-$(CONFIG_HIBERNATION)  += hibernate.o snapshot.o swap.o user.o \
                   block_io.o //这个目前没有定义,所以PM_HIBERNATION_PREPARE等还没有!!
obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o
obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o
obj-$(CONFIG_SUSPEND_TIME) += suspend_time.o
obj-$(CONFIG_MAGIC_SYSRQ)  += poweroff.o
obj-$(CONFIG_SUSPEND)  += wakeup_reason.o

//kernel/driver/power/Makefile
obj-$(CONFIG_PM)   += sysfs.o generic_ops.o common.o qos.o
obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
obj-$(CONFIG_PM_RUNTIME)   += runtime.o //
obj-$(CONFIG_PM_TRACE_RTC) += trace.o
obj-$(CONFIG_PM_OPP)   += opp.o
obj-$(CONFIG_PM_GENERIC_DOMAINS)   +=  domain.o domain_governor.o
obj-$(CONFIG_HAVE_CLK) += clock_ops.o

ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG

Linux wake_lock的实现

1) 内核中调用wake_lock函数,其定义在wakelock.h文件中

static inline void wake_lock(struct wake_lock *lock)
{
    __pm_stay_awake(&lock->ws);
}

CONFIG_PM_WAKELOCKS使能之后,会向上层提供wake_lock节点

#ifdef CONFIG_PM_WAKELOCKS
static ssize_t wake_lock_show(struct kobject *kobj,
                  struct kobj_attribute *attr,
                  char *buf)
{
    return pm_show_wakelocks(buf, true);
}

static ssize_t wake_lock_store(struct kobject *kobj,
                   struct kobj_attribute *attr,
                   const char *buf, size_t n)
{
    int error = pm_wake_lock(buf); //这个函数也是调用上面提供的__pm_stay_awake()函数!
    return error ? error : n;
}

power_attr(wake_lock);

static ssize_t wake_unlock_show(struct kobject *kobj,
                struct kobj_attribute *attr,
                char *buf)
{
    return pm_show_wakelocks(buf, false);
}

static ssize_t wake_unlock_store(struct kobject *kobj,
                 struct kobj_attribute *attr,
                 const char *buf, size_t n)
{
    int error = pm_wake_unlock(buf);
    return error ? error : n;
}

power_attr(wake_unlock);

从上面的代码可以看到,wake_lock就是调用__pm_stay_awake()函数来完成阻止系统进入睡眠的功能。
__pm_stay_awake() 怎么防止系统进入syspend状态的呢??这些都是通过wakeup_source来完成的。
如果某个驱动想要阻止,允许进入睡眠,可以通过wakeup_source_init(struct wakeup_source *ws,const char *name)函数初始化一个wake source并调用__pm_stay_awake() ,或者对应地使用 __pm_relax()来阻止/允许进入睡眠。
这个wake_source在suspend流程中可以看到,在try_to_suspend()函数中会调用check wakeup_sources()来检查wake_source并决定是否进入pm_suspend()。

Linux进入suspend状态的路径

上面说wake_lock怎么去阻止系统进入suspend状态的,还需要解释一下Linux进入syspend状态的流程。
1)一个是/sys/power/state中写入mem或者on等进行suspend或者唤醒(/kernel/kernel/power/main.c)
这个过程需要再分析PowerManagerService(??)
2) 从/kernel/kernel/power/autosleep.c文件中的try_to_suspend()函数,这个也是PowerManagerService
从/sys/power/autosleep发来的(??)。

root@xxx:/sys/power # ls
ls
autosleep
cpufreq_max_limit
cpufreq_min_limit
cpufreq_table
pm_async
pm_freeze_timeout
state
wake_lock
wake_unlock
wakeup_count

当然这些过程都会检查wake source是否全是deactive状态。wakesource有active和deactive两种状态,当/sys/power/autosleep或者/sys/power/state写入mem等准备进入suspend的时候都会去检查wakesource链表中的所有wake source是否都处于deactive状态,是的话才会进入suspend状态。wake lock的实现也是封装了wake source实现的,用红黑树和LRU管理。wake source会在300秒之后被从wake lock的链表中移除(??)

Android系统中获取释放wake_lock

Android中wakelock的获取像上图所示。获取wake lock的代码如下:

1) 获取wake lock

final PowerManager powerManager = (PowerManager) context.getSystemService(
               Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
...
mWakeLock.acquire();
mWakeLock.setReferenceCounted(false); ////使用非计时Wakelock
...

2) 释放wake lock
mWakeLock.release();

上面的PARTIAL_WAKE_LOCK似乎和内核没有什么联系,具体怎么实现的以后再看?????????。

下面是几种上层实现的锁的解释!!

newWakeLock(int flags, String tag);//取得相应层次的锁 
flags参数说明:
PARTIAL_WAKE_LOCK: Screen off, keyboard light off
//保持CPU运转,允许屏幕和键盘灯有可能是关闭的

SCREEN_DIM_WAKE_LOCK: screen dim, keyboard light off
//保持CPU运转,保持屏幕显示但有可能是灰的,允许关闭键盘灯

SCREEN_BRIGHT_WAKE_LOCK: screen bright, keyboard light off
//保持CPU运转,保持屏幕高亮显示,允许关闭键盘灯

FULL_WAKE_LOCK: screen bright, keyboard bright
//保持CPU运转,保持屏幕高亮显示,键盘灯也保持亮度

ACQUIRE_CAUSES_WAKEUP: 一旦有请求锁时强制打开Screen和keyboard light

ON_AFTER_RELEASE: 在释放锁时reset activity timer

Note:
如果申请了partial wakelock,那么即使按Power键,系统也不会进Sleep,如Music播放时
如果申请了其它的wakelocks,按Power键,系统还是会进Sleep

如果Screen off timer时间到并且没有Full wake lock或者用户按了power key,那么系统状态将被切换到
NOTIFICATION,并且调用所有已经注册的g_early_suspend_handlers函数, 通常会把LCD和Backlight驱动
注册成early suspend类型,如有需要也可以把别的驱动注册成early suspend, 这样就会在第一阶段被关闭。
接下来系统会判断是否有partial wake lock acquired, 如果有则等待其释放, 在等待的过程中如果有
user activity事件发生,系统则马上回到AWAKE状态;如果没有partial wake lock acquired, 则再释放
main_wake_lock系统开机时定义的内核态唤醒锁,然后系统会马上调用函数pm_suspend关闭其它相关的驱动, 
让CPU进入休眠状态(进入Linux标准的内核休眠).

3) 权限获取
要进行电源的操作需要在AndroidManifest.xml中声明该应用有设置电源管理的权限。

<uses-permissionandroid:name="android.permission.WAKE_LOCK"/>你可能还需要
<uses-permission android:name="android.permission.DEVICE_POWER"/>

以下是上面的acquire函数是怎么一步步调用到内核的wake_lock节点并获取wake_lock的步骤

mWakeLock.acquire()->PowerManager.acquireLocked()->
PowerManagerServer.acquireWakeLock()->
PowerManagerServer.acquireWakeLockInternal()->
PowerManagerServer.updatePowerStateLocked()->
PowerManagerServer.updateSuspendBlockerLocked()->
PowerManagerServer.java文件中的acquire()->
nativeAcquireSuspendBlocker()函数

Linux, Android电源管理:wakelock,autosleep_第1张图片

然后最终调用到android/hardware/libhardware_legacy/power/power.c文件中的
acquire_wake_lock()函数

int acquire_wake_lock(int lock, const char* id)
{
    initialize_fds();

// ALOGI("acquire_wake_lock lock=%d id='%s'\n", lock, id);

    if (g_error) return g_error;

    int fd;

    if (lock == PARTIAL_WAKE_LOCK) {
        fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];
    }
    else {
        return EINVAL;
    }

    return write(fd, id, strlen(id));
}

//以下是打开Linux内核向上层提供的节点的函数,可以看到打开的节点就是
/* const char * const NEW_PATHS[] = { "/sys/power/wake_lock", "/sys/power/wake_unlock", }; */
static inline void initialize_fds(void)
{
    // XXX: should be this:
    //pthread_once(&g_initialized, open_file_descriptors);
    // XXX: not this:
    if (g_initialized == 0) {
        if(open_file_descriptors(NEW_PATHS) < 0)
            open_file_descriptors(OLD_PATHS);
        g_initialized = 1;
    }
}

参考:http://www.wowotech.net/linux_kenrel/wakeup_events_framework.html

Linux,Android autosleep

Linux内核版本3.5和Android版本4.3开始已经不再使用Early suspend。而是都换成了autosleep。
kernel断CONFIG_HAS_EARLYSUSPEND也被disable掉换成了CONFIG_PM_AUTOSLEEP。
内核的实现和上面说的wakelock等都很像,都是/kernel/kernel/power/目录下的,只是有一个叫autosleep.c的文件负责创建autosleept节点给上层。

autosleep替换early suspend之后。之前使用early suspend的设备(比如TSP),现在都只能使用autosleep机制来是先了,已经不在内核注册early suspend等等,这部分被从内核移除。
之前early suspend上的resume和suspend函数被input_dev->open和input_dev->close()所取代。调用这些内容的也从内核去掉,移到上册HAL里边。但一直没有找到在哪里调用input_dev->open和input_dev->close()。比如关掉屏幕等操作的时候,是有DisplayManagerService.java文件中的BlankUnblankInSeperateThread进程在进行input_dev->open和input_dev->close()的调用,完成类似之前early suspend的功能的。

input_dev->open()和input_dev->close都是通过input_open_device()和input_close_device()调用下来。
而input_open_device和input_close_device()就是往/sys/class/input/event*/device/enabled节点里边写1或者写0来完成的。当然,需要睡眠的输入输出设备是在InputReader.cpp文件的addDeviceLocked()函数中加的。

void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
    const char* deviceName = identifier.name.string();
    //比如三星的设备,其触摸屏或者pen都是起来下面的名字。看到这些名字的输入输出设备就调用如下
    //notifySuspendDevice()函数使能这个设备!!
    if (strstr(deviceName, "sec_touchkey") || strstr(deviceName, "sec_touchscreen") || 
                            strstr(deviceName, "sec_e-pen")) {
        const char* path = mEventHub->getDevicePath(deviceId);
        if (path != NULL) {
            ALOGD("reporting suspendable device %s as number %s", deviceName, path + 16);
            mPolicy->notifySuspendDevice(path + 16, deviceName); 
                // 16 character should be excluded ("/dev/input/event")
        }
    }
    ...
}
//com_android_input_InputManagerService.cpp
void NativeInputManager::notifySuspendDevice(const char* number, const char* deviceName) {
    android_server_PowerManagerService_addDevice(number, deviceName);

    //[@input_fw disable suspendible device in case of sidesync
    String8 path;
    path.append("/sys/class/input/event");
    path.append(number);
    path.append("/device/enabled");
    String8 name;
    name.append(deviceName);

    JNIEnv* env = jniEnv();

    ScopedLocalRef<jstring> pathObj(env, env->NewStringUTF(path.string()));
    ScopedLocalRef<jstring> nameObj(env, env->NewStringUTF(name.string()));
    env->CallVoidMethod(mServiceObj, 
        gServiceClassInfo.getSuspendibleDevices, pathObj.get(), nameObj.get());
    checkAndClearExceptionFromCallback(env, "getSuspendibleDevices");
    //]
}
//上面的函数读/sys/class/input/event*/device/enabled文件并调用InputManagerService.java中的
//getSuspendibleDevices()函数保存在mSuspendibleDevices中。
//setSuspendibleDevices()函数就是mSuspendibleDevices中读取可以睡眠的输入输出设备,设置往其设备节点中的
//enable节点写值,就会调用input_dev->open函数或者input_dev->close。

//[@input_fw disable suspendible device in case of sidesync
private void getSuspendibleDevices(String path, String name) {
    mSuspendibleDevices.put(name, path);
    Log.d(TAG, "get suspendable device " + name + " as path " + path);
}

private boolean setSuspendibleDevices(String device, boolean enable) {
    if (mSuspendibleDevices.isEmpty()) {
        Log.d(TAG, "Not exist SuspendibleDevices");
        return false;
    }

    if (device.equals("all")) {
        String[] targetdevices = new String[mSuspendibleDevices.size()];
        mSuspendibleDevices.keySet().toArray(targetdevices);

        for(String targetdevice:targetdevices) {
            sysfsWrite(mSuspendibleDevices.get(targetdevice), (enable ? 1 : 0));
            Log.d(TAG, targetdevice + " is " + (enable ? "enabled" : "disabled"));
        }
    return true;
    } else if (mSuspendibleDevices.containsKey(device)) {
        sysfsWrite(mSuspendibleDevices.get(device), (enable ? 1 : 0));
        Log.d(TAG, device + " is " + (enable ? "enabled" : "disabled"));
        return true;
    }
    return false;
}

关于Early suspend:
http://blog.csdn.net/droidphone/article/details/6642081

http://blog.csdn.net/wlwl0071986/article/details/9746135

http://blog.csdn.net/wlsfling/article/details/46005409

http://blog.csdn.net/u010681466/article/details/19897851

设置背光LED灯

frameworks/base/services/core/java/com/android/server/lights/LightsService.java,
frameworks/base/services/core/java/com/android/server/lights/Light.java

DisplayPowerState.java等需要管理背光和LED等的地方,都是调用setBrightness()函数来实现。
setBrightness()函数实现在LightsService.java文件中。调用顺序如下:

1. setBrightness()
[frameworks/base/services/core/java/com/android/server/lights/LightsService.java]
2. setLight_native()
[frameworks/base/services/core/jni/com_android_server_lights_LightsService.cpp]
3.set_light()[android/hardware/qcom/display/liblight/Lights.c](高通)

Lights.c文件中定义了所有LED灯的路径,有相关的驱动变更需要查看相关驱动的路径等~
char const*const RED_LED_FILE
        = "/sys/class/leds/red/brightness";
char const*const GREEN_LED_FILE
        = "/sys/class/leds/green/brightness";
char const*const BLUE_LED_FILE
        = "/sys/class/leds/blue/brightness";
char const*const LCD_FILE
        = "/sys/class/leds/lcd-backlight/brightness";
char const*const BUTTON_FILE
        = "/sys/class/leds/button-backlight/brightness";
char const*const RED_BLINK_FILE
        = "/sys/class/leds/red/blink";
char const*const GREEN_BLINK_FILE
        = "/sys/class/leds/green/blink";
char const*const BLUE_BLINK_FILE
        = "/sys/class/leds/blue/blink";

你可能感兴趣的:(android,linux,PM)