android 和linux的休眠唤醒机制

Android在标准linux基础上对休眠唤醒的实现(三)
Linux Android JNI OS Go

四、android层源码解析

在linux之上经过android的软件堆层层封装,最终在上层的java应用程序中使用。休眠唤醒也是从最上层发出的命令,然后一层一层地将参数解析,往最底层传,最后走上标准linux的休眠唤醒之路。

这一部分将会初略分析休眠唤醒机制上linux之上所走的路线。

在linux之上,存在一个hal层,专门做和linux内核设备打交道的事情,这里也不例外。休眠唤醒机制的hal层源码位于:@hardware/libhardware_legacy/power/power.c

该文件源码比较简单,下面列举重点片段:

enum {

ACQUIRE_PARTIAL_WAKE_LOCK = 0,

RELEASE_WAKE_LOCK,

REQUEST_STATE,

OUR_FD_COUNT

};

const char * const NEW_PATHS[] = {

"/sys/power/wake_lock",

"/sys/power/wake_unlock",

"/sys/power/state"

};

static int g_initialized = 0;

static int g_fds[OUR_FD_COUNT];

static const char *off_state = "mem";

static const char *on_state = "on";

static int open_file_descriptors(const char * const paths[])

{

int i;

for (i=0; i

int fd = open(paths[i], O_RDWR);

if (fd < 0) {

fprintf(stderr, "fatal error opening \"%s\"\n", paths[i]);

g_error = errno;

return -1;

}

g_fds[i] = fd;

}

g_error = 0;

return 0;

}

static inline void initialize_fds(void)

{

if (g_initialized == 0) {

if(open_file_descriptors(NEW_PATHS) < 0) {

open_file_descriptors(OLD_PATHS);

on_state = "wake";

off_state = "standby";

}

g_initialized = 1;

}

}

int acquire_wake_lock(int lock, const char* id)

{

initialize_fds();

if (g_error) return g_error;

int fd;

if (lock == PARTIAL_WAKE_LOCK) { // 上层传下来的lock type

fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];

}

else {

return EINVAL;

}

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

}

int release_wake_lock(const char* id)

{

initialize_fds();

// LOGI("release_wake_lock id='%s'\n", id);

if (g_error) return g_error;

ssize_t len = write(g_fds[RELEASE_WAKE_LOCK], id, strlen(id));

return len >= 0;

}

int set_screen_state(int on)

{

QEMU_FALLBACK(set_screen_state(on));

LOGI("*** set_screen_state %d", on);

initialize_fds();

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;

}

Hal层的代码在jni层中被使用,源码位于:frameworks/base/core/jni/android_os_Power.cpp,代码片段如下:

static void acquireWakeLock(JNIEnv *env, jobject clazz, jint lock, jstring idObj)

{

if (idObj == NULL) {

throw_NullPointerException(env, "id is null");

return ;

}

const char *id = env->GetStringUTFChars(idObj, NULL);

acquire_wake_lock(lock, id);

env->ReleaseStringUTFChars(idObj, id);

}// 对wakelock加锁函数

static void releaseWakeLock(JNIEnv *env, jobject clazz, jstring idObj)

{

if (idObj == NULL) {

throw_NullPointerException(env, "id is null");

return ;

}

const char *id = env->GetStringUTFChars(idObj, NULL);

release_wake_lock(id);

env->ReleaseStringUTFChars(idObj, id);

}// 对wakelock解锁函数

static int setScreenState(JNIEnv *env, jobject clazz, jboolean on)

{

return set_screen_state(on);

}// 休眠唤醒的函数

Jni的方法需要注册到上层才可以使用,同时也需要在上层的对应java类中声明了native才可以使用。那么这里的方法在java中对应的声明在哪里呢?frameworks/base/core/java/android/os/Power.java,该文件定义一个java类,如下:

public class Power

{

// can't instantiate this class

private Power()

{

}

/**

* Wake lock that ensures that the CPU is running. The screen might

* not be on.

*/

public static final int PARTIAL_WAKE_LOCK = 1;

/**

* Wake lock that ensures that the screen is on.

*/

public static final int FULL_WAKE_LOCK = 2;

public static native void acquireWakeLock(int lock, String id);

public static native void releaseWakeLock(String id);

/**

* Turn the screen on or off

*

* @param on Whether you want the screen on or off

*/

public static native int setScreenState(boolean on);

}

声明的jni接口应该是被java server在使用,这里就是专门的电源管理服务:PowerManagerService使用,具体源码位置在:frameworks/base/services/java/com/android/server/PowerManagerService.java。android在最上层还提供了现场的android.os.PowerManager类

(frameworks/base/core/java/android/os/PowerManager.java)来供app使用,PowerManager类会调用java服务PowerManagerService的方法来完成与wakelock相关的工作。

@ frameworks/base/core/java/android/os/PowerManager.java

类PowerManager中内嵌了一个WakeLock类,另外还定义了wakelock的类型,下面是代码片段:

public class PowerManager

{

private static final String TAG = "PowerManager";

/**

* Wake lock that ensures that the CPU is running. The screen might

* not be on.

*/

public static final int PARTIAL_WAKE_LOCK = WAKE_BIT_CPU_STRONG;

/**

* Wake lock that ensures that the screen and keyboard are on at

* full brightness.

*/

public static final int FULL_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT | WAKE_BIT_KEYBOARD_BRIGHT;

/**

* Wake lock that ensures that the screen is on at full brightness;

* the keyboard backlight will be allowed to go off.

*/

public static final int SCREEN_BRIGHT_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT;

/**

* Wake lock that ensures that the screen is on (but may be dimmed);

* the keyboard backlight will be allowed to go off.

*/

public static final int SCREEN_DIM_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_DIM;

/**

* Wake lock that turns the screen off when the proximity sensor activates.

* Since not all devices have proximity sensors, use

* {@link #getSupportedWakeLockFlags() getSupportedWakeLockFlags()} to determine if

* this wake lock mode is supported.

*

* {@hide}

*/

public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK

= WAKE_BIT_PROXIMITY_SCREEN_OFF;

public class WakeLock

{

WakeLock(int flags, String tag)

{

switch (flags & LOCK_MASK) {

case PARTIAL_WAKE_LOCK:

case SCREEN_DIM_WAKE_LOCK:

case SCREEN_BRIGHT_WAKE_LOCK:

case FULL_WAKE_LOCK:

case PROXIMITY_SCREEN_OFF_WAKE_LOCK:

break;

default:

throw new IllegalArgumentException();

}

mFlags = flags;

mTag = tag;

mToken = new Binder();

}

public void acquire()

{

synchronized (mToken) {

if (!mRefCounted || mCount++ == 0) {

try {

mService.acquireWakeLock(mFlags, mToken, mTag);

} catch (RemoteException e) {

}

mHeld = true;

}

}

}

public void release(int flags)

{

synchronized (mToken) {

if (!mRefCounted || --mCount == 0) {

try {

mService.releaseWakeLock(mToken, flags);

} catch (RemoteException e) {

}

mHeld = false;

}

if (mCount < 0) {

throw new RuntimeException("WakeLock under-locked " + mTag);

}

}

}

}

public WakeLock newWakeLock(int flags, String tag)

{

if (tag == null) {

throw new NullPointerException("tag is

null in PowerManager.newWakeLock");

}

return new WakeLock(flags, tag);

}

public void goToSleep(long time)

{

try {

mService.goToSleep(time);

} catch (RemoteException e) {

}

}

public PowerManager(IPowerManager service, Handler handler)

{

mService = service;

  • 21:40
  • 评论 / 浏览 (0 / 378)
2011-05-29
缩略显示

Android在标准linux基础上对休眠唤醒的实现(二)

Linux Android 活动 工作

三、kernel层源码解析 - wakelock的重要地位

wakelock在android的休眠唤醒机制中扮演着及其重要的角色,主要源码位于文件:kernel/kernel/power/wakelock.c,kernel/include/linux/wakelock.h中。

wakelocks_init()函数所做的工作是整个wakelock可以工作起来的基础,所有这里先说说这个函数。

static int __init wakelocks_init(void)

{

int ret;

int i;

for (i = 0; i < ARRAY_SIZE(active_wake_locks); i++)

INIT_LIST_HEAD(&active_wake_locks[i]);

// 初始化active_wake_locks数组中的两个类型锁链表: WAKE_LOCK_SUSPEND,WAKE_LOCK_IDLE

#ifdef CONFIG_WAKELOCK_STAT // defined

wake_lock_init(&deleted_wake_locks, WAKE_LOCK_SUSPEND,

"deleted_wake_locks");

// 初始化wakelock deleted_wake_locks,同时将其加入到非活动锁链表中

#endif

wake_lock_init(&main_wake_lock, WAKE_LOCK_SUSPEND, "main");

wake_lock_init(&sys_sync_wake_lock, WAKE_LOCK_SUSPEND, "sys_sync");

wake_lock(&main_wake_lock);

wake_lock_init(&unknown_wakeup, WAKE_LOCK_SUSPEND, "unknown_wakeups");

// 初始化wakelock: main, sys_sync, unknown_wakeups, 同时将其加入到非活动锁链表中

// 给 main_wake_lock 加锁

ret = platform_device_register(&power_device);

if (ret) {

pr_err("[wakelocks_init]: platform_device_register failed\n");

goto err_platform_device_register;

}

ret = platform_driver_register(&power_driver);

if (ret) {

pr_err("[wakelocks_init]: platform_driver_register failed\n");

goto err_platform_driver_register;

}

// 新建工作队列和工作者内核线程: sys_sync_work_queue, fs_sync

// suspend_work_queue, suspend

sys_sync_work_queue = create_singlethread_workqueue("fs_sync");

if (sys_sync_work_queue == NULL) {

pr_err("[wakelocks_init] fs_sync workqueue create failed\n");

}

suspend_work_queue = create_singlethread_workqueue("suspend");

if (suspend_work_queue == NULL) {

ret = -ENOMEM;

goto err_suspend_work_queue;

}

#ifdef CONFIG_WAKELOCK_STAT

proc_create("wakelocks", S_IRUGO, NULL, &wakelock_stats_fops);

// 创建proc接口

#endif

return 0;

err_suspend_work_queue:

platform_driver_unregister(&power_driver);

err_platform_driver_register:

platform_device_unregister(&power_device);

err_platform_device_register:

wake_lock_destroy(&unknown_wakeup);

wake_lock_destroy(&main_wake_lock);

#ifdef CONFIG_WAKELOCK_STAT

wake_lock_destroy(&deleted_wake_locks);

#endif

return ret;

}

可以看到该初始化函数中新建了几个wakelock: deleted_wake_locks、main_wake_lock、sys_sync_wake_lock、unknown_wakeup,他们全部都是WAKE_LOCK_SUSPEND类型的wakelock,说到这里不得不提到wakelock的两种类型了:

1. WAKE_LOCK_SUSPEND – 这种锁如果被某个task持有,那么系统将无法进入休眠。

2. WAKE_LOCK_IDLE – 这种锁不会影响到系统进入休眠,但是如果这种锁被持有,那么系统将无法进入idle空闲模式。

不过常用的所类型还是WAKE_LOCK_SUSPEND,包括userwakelock.c提供给用户空间的新建wakelock的接口,都是建立的第一种锁。另外系统为了分开管理这两种不同类型的锁,建立了两个链表来统一链接不同类型的锁:active_wake_locks[],这个是具有两个链表头的数组,元素0是挂接WAKE_LOCK_SUSPEND类型的锁,而元素1就是挂接WAKE_LOCK_IDLE类型的wakelock了。

接着上面说,这个初始化函数新建这些锁之后,直接将主锁(main_wake_lock)给上锁了,其余都是非锁状态。新建wakelock使用函数wake_lock_init(),该函数设置锁的名字,类型,最后将新建的锁挂接到一个专门链接这些非锁状态的链表inactive_locks上(新建的wakelock初期都是出于非锁状态的,除非显示调用函数wake_lock来上锁)。接着如果使用函数wake_lock()来给特定的wakelock上锁的话,会将该锁从链表inactive_locks上移动到对应类型的专用链表上active_wake_locks[type]上。

wakelock有两种形式的锁:超时锁和非超时锁,这两种形式的锁都是使用函数wake_lock_init()来初始化,只是在上锁的时候会有一点点差别,超时锁使用函数wake_lock_timeout(),而非超时锁使用函数wake_lock(), 这个两个函数会最终调用到同一个函数wake_lock_internal(),该函数依靠传入的不同参数来选择不同的路径来工作。值得注意的是,非超时锁必须手工解锁,否则系统永远不能进入睡眠。下面是wake_lock_internal()函数的片段:

if (!(lock->flags & WAKE_LOCK_ACTIVE))

lock->flags |= WAKE_LOCK_ACTIVE;// wakelock状态为inactive,则更改为active

if (has_timeout) { // wake_lock_timeout()会传入1

if (wakelock_debug_mask & DEBUG_WAKE_LOCK)

pr_info("[wake_lock_internal]: %s, type %d, timeout %ld.%03lu\n",

lock->name, type, timeout / HZ,

(timeout % HZ) * MSEC_PER_SEC / HZ);

lock->expires = jiffies + timeout; // 设置超时时间

lock->flags |= WAKE_LOCK_AUTO_EXPIRE; // 超时锁标志

list_add_tail(&lock->link, &active_wake_locks[type]);

}

// acquire a non-timeout wakelock 添加一个非超时锁

else { // wake_lock ()会传入0

if (wakelock_debug_mask & DEBUG_WAKE_LOCK)

pr_info("[wake_lock_internal]: %s, type %d\n", lock->name, type);

lock->expires = LONG_MAX; // 设置成超时时间最大值

lock->flags &= ~WAKE_LOCK_AUTO_EXPIRE; // 非超时锁标志

list_add(&lock->link, &active_wake_locks[type]);

// 将刚刚设置的非超时锁加到对应类型的活动锁链表中

}

解锁的时候,这两种形式的锁所使用函数都是一样了:wake_unlock(),该函数中会首先作如下操作:

lock->flags &= ~(WAKE_LOCK_ACTIVE | WAKE_LOCK_AUTO_EXPIRE);

// 清除锁活动标志和自动超时标志

list_del(&lock->link); // 从锁对应的活动链表上摘除

list_add(&lock->link, &inactive_locks);

// 将unlock的锁挂接到非活动链表inactive_locks上

前面已经说了只有类型为WAKE_LOCK_SUSPEND的wakelock被上锁才会阻止系统进入suspend,那么也就是说只要链表active_wake_locks[WAKE_LOCK_SUSPEND]为NULL,那么系统就可以执行suspend的流程了。Android对linux的改造,让其可以在三种情况下进入linux的标准suspend的流程:

1. wake_unlock(),这个应该是最容易想到的,只要系统有对WAKE_LOCK_SUSPEND类型的wakelock解锁的动作,都有可能会进入suspend流程开始休眠,为什么是有可能呢?因为可能还有超时锁没有被超时解锁。下面看一下代码片段:

void wake_unlock(struct wake_lock *lock)

{

if (type == WAKE_LOCK_SUSPEND) // 貌似只在处理这个类型的wakelock

{

long has_lock = has_wake_lock_locked(type);

// 这个函数蛮重要,它来检查type类型的链表上是否还有锁被上锁了。

// 其返回值如果是0,说明没有该类型的锁被持有了;返回非0表明就是这个类型的活动链表上还存在超时锁但是没有非超时锁了,这个返回值就是当前时间距离最后超时的锁超时时间的jiffies值;如果返回-1,那表明还有该类型的非超时锁被持有。

if (wakelock_debug_mask & DEBUG_WAKE_LOCK)

pr_info("[wake_unlock]: has_lock = 0x%x\n" , has_lock);

if (has_lock > 0) {

if (wakelock_debug_mask & DEBUG_EXPIRE)

pr_info("[wake_unlock]: %s, start expire timer, "

"%ld\n", lock->name, has_lock);

mod_timer(&expire_timer, jiffies + has_lock);

// 修改定时器的超时值并add该定时器

}

else // 已经没有超时锁了

{

if (del_timer(&expire_timer)) // 删除定时器

if (wakelock_debug_mask & DEBUG_EXPIRE)

pr_info("[wake_unlock]: %s, stop expire "

"timer\n", lock->name);

if (has_lock == 0)

// !=0,表明还有该类型的非超时锁被持有,现在还不能进入suspend

{

pr_info("[wake_unlock]: (%s) suspend_work_queue suspend_work\n" , lock->name);

queue_work(suspend_work_queue, &suspend_work);

// 提交suspend的工作项,开始执行标准linux的suspend流程

}

}

}

spin_unlock_irqrestore(&list_lock, irqflags);

}

2. 超时锁超时之后,定时器的回调函数会执行会查看是否有其他的wakelock, 如果没有, 就在这里让系统进入睡眠。

static void expire_wake_locks(unsigned long data)

{

long has_lock;

unsigned long irqflags;

if (debug_mask & DEBUG_EXPIRE)

pr_info("expire_wake_locks: start\n");

spin_lock_irqsave(&list_lock, irqflags);

if (debug_mask & DEBUG_SUSPEND)

print_active_locks(WAKE_LOCK_SUSPEND);

has_lock = has_wake_lock_locked(WAKE_LOCK_SUSPEND);

if (debug_mask & DEBUG_EXPIRE)

pr_info("expire_wake_locks: done, has_lock %ld\n", has_lock);

if (has_lock == 0)

// 如果没有SUSPEND类型的wakelock处于active,那么将调用suspend

queue_work(suspend_work_queue, &suspend_work);

spin_unlock_irqrestore(&list_lock, irqflags);

}

static DEFINE_TIMER(expire_timer, expire_wake_locks, 0, 0);

列出以下一个重要的函数源码:

static long has_wake_lock_locked(int type)

{

struct wake_lock *lock, *n;

long max_timeout = 0;

BUG_ON(type >= WAKE_LOCK_TYPE_COUNT);

list_for_each_entry_safe(lock, n, &active_wake_locks[type], link) {

if (lock->flags & WAKE_LOCK_AUTO_EXPIRE) {

long timeout = lock->expires - jiffies;

if (timeout <= 0)

expire_wake_lock(lock);

else if (timeout > max_timeout)

max_timeout = timeout;

} else

return -1;

}

return max_timeout;

}

3. 这个可能有人觉得匪夷所思,就是在wake_lock{_ _timeout}()函数中,调用了内部函数wake_lock_internal()。这里只有在对超时锁上锁的时候才有可能进入休眠,如果对一个费超时锁上锁的话,那么就没有必要去检查活动链表了。

static void wake_lock_internal(

struct wake_lock *lock, long timeout, int has_timeout)

{

if (type == WAKE_LOCK_SUSPEND) {

current_event_num++;

#ifdef CONFIG_WAKELOCK_STAT

if (lock == &main_wake_lock)

update_sleep_wait_stats_locked(1);

else if (!wake_lock_active(&main_wake_lock))

update_sleep_wait_stats_locked(0);

#endif

if (has_timeout) // 超时锁的时候传进来的是1

expire_in = has_wake_lock_locked(type);

// 检查当前锁类型链表上是否还有锁处于active的状态,无返回0

else

expire_in = -1;

// 如果是非超时锁的话,这里直接赋值-1,省去了活动链表检查步骤了

if (expire_in > 0) {

if (debug_mask & DEBUG_EXPIRE)

pr_info("wake_lock: %s, start expire timer, "

"%ld\n", lock->name, expire_in);

// modify the time wakelock is expired

mod_timer(&expire_timer, jiffies + expire_in);

} else {

if (del_timer(&expire_timer))

if (debug_mask & DEBUG_EXPIRE)

  • 21:33
  • 评论 / 浏览 (0 / 860)
2011-05-29
缩略显示

新版linux系统设备架构中关于电源管理方式的变更

Linux 数据结构

新版linux系统设备架构中关于电源管理方式的变更
based on linux-2.6.32

一、设备模型各数据结构中电源管理的部分

linux的设备模型通过诸多结构体来联合描述,如struct device,struct device_type,struct class,
struct device_driver,struct bus_type等。

@kernel/include/linux/devices.h中有这几中结构体的定义,这里只列出和PM有关的项,其余查看源码:

struct device{
...
struct dev_pm_infopower;
...
}

struct device_type {
...
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, mode_t *mode);
void (*release)(struct device *dev);

const struct dev_pm_ops *pm;
};

struct class {
...
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;
...
};

struct device_driver {
...
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);

const struct dev_pm_ops *pm;
...
};

struct bus_type {
...
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;
...
};

以上可以看出和电源管理相关的两个结构体是struct dev_pm_info和struct dev_pm_ops,他们定义于文件
@kernel/include/linux/pm.h

struct dev_pm_info {
pm_message_tpower_state;
unsigned intcan_wakeup:1;
unsigned intshould_wakeup:1;
enum dpm_statestatus;/* Owned by the PM core - 表示该设备当前的PM状态*/
#ifdef CONFIG_PM_SLEEP
struct list_headentry;/* 链接到dpm_list全局链表中的连接体 */
#endif
#ifdef CONFIG_PM_RUNTIME// undef
struct timer_listsuspend_timer;
unsigned longtimer_expires;
struct work_structwork;
wait_queue_head_twait_queue;
spinlock_tlock;
atomic_tusage_count;
atomic_tchild_count;
unsigned intdisable_depth:3;
unsigned intignore_children:1;
unsigned intidle_notification:1;
unsigned intrequest_pending:1;
unsigned intdeferred_resume:1;
enum rpm_requestrequest;
enum rpm_statusruntime_status;
intruntime_error;
#endif
};

struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};

二、device中的dev_pm_info结构体

device结构体中的power项用来将该设备纳入电源管理的范围,记录电源管理的一些信息。
在注册设备的时候调用函数device_add()来向sysfs系统添加power接口和注册进电源管理系统,代码片段如下:
...
error = dpm_sysfs_add(dev);@kernel/drivers/base/power/sysfs.c
if (error)
goto DPMError;
device_pm_add(dev);@kernel/drivers/base/power/main.c
...
其中dpm_sysfs_add()函数用来向sysfs文件系统中添加相应设备的power接口文件,如注册mt6516_tpd paltform device的时候,会在sysfs中出现如下目录和文件:
#pwd
/sys/devices/platform/mt6516-tpd
#cd mt6516-tpd
#ls -l
-rw-r--r-- root root 4096 2010-01-02 06:35 uevent
-r--r--r-- root root 4096 2010-01-02 06:39 modalias
lrwxrwxrwx root root 2010-01-02 06:39 subsystem -> ../../../bus/platform
drwxr-xr-x root root 2010-01-02 06:35 power
lrwxrwxrwx root root 2010-01-02 06:39 driver -> ../../../bus/platform/drivers/mt6516-tpd
#cd power
#ls -l
-rw-r--r-- root root 4096 2010-01-02 06:39 wakeup

源码片段:
static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store);

static struct attribute * power_attrs[] = {
&dev_attr_wakeup.attr,
NULL,
};
static struct attribute_group pm_attr_group = {
.name= "power",// attribute_group结构体的name域不为NULL的话,都会已name建立一个属性目录的
.attrs= power_attrs,
};

int dpm_sysfs_add(struct device * dev)
{
return sysfs_create_group(&dev->kobj, &pm_attr_group);//在当前device的kobject结构体对应的目录下建立
}

其中的device_pm_add()函数会将该设备插入到电源管理的核心链表dpm_list中统一管理。
值得一提的是,在函数device_initialize()会调用函数device_pm_init()来初始化该device结构体的power域:
dev->power.status = DPM_ON;

void device_pm_add(struct device *dev)
{
...
mutex_lock(&dpm_list_mtx);
if (dev->parent) {
if (dev->parent->power.status >= DPM_SUSPENDING)
// 如果某设备处于DPM_SUSPENDING极其之后的状态,此时不允许以该设备为父设备注册子设备
dev_warn(dev, "parent %s should not be sleeping\n", dev_name(dev->parent));
} else if (transition_started) { // transition_started全局变量包含在PM transition期间不允许注册设备
/*
* We refuse to register parentless devices while a PM
* transition is in progress in order to avoid leaving them
* unhandled down the road
*/
dev_WARN(dev, "Parentless device registered during a PM transaction\n");
}

list_add_tail(&dev->power.entry, &dpm_list);// 将device结构体通过power.entry项链接进dpm_list
mutex_unlock(&dpm_list_mtx);
}

void device_pm_remove(struct device *dev)
{
...
mutex_lock(&dpm_list_mtx);
list_del_init(&dev->power.entry);
mutex_unlock(&dpm_list_mtx);
pm_runtime_remove(dev);
}

举例说明:

我们熟知的platform bus在系统中也是作为一种设备注册进了系统,在sysfs文件系统中的位置是:
/sys/devices/platform。使用函数device_register(&platform_bus)进行注册,调用device_add()函数,
注册ok之后,也会出现目录/sys/devices/platform/power。最后也会将其添加进dpm_list中。

i2c控制器外设代表的设备是注册在platform总线上的,也就是说它的父设备是platform。
最终在platform_device_add()中会调用函数device_add()函数来添加设备,最终也会在mt6516-i2c.0/
mt6516-i2c.1/mt6516-i2c.2中出现一个power目录,同时这3个platform设备会依靠
platform_device.dev.power.entry连接件链接到电源管理核心链表dpm_list中。
/sys/devices/platform/mt6516-i2c.2/power

每一个i2c控制器都会在系统中至少注册成一个适配器(adapter),该结构体将会间接提供给i2c设备的驱动来使用,以避免直接使用i2c控制器结构体。这个适配器没有对应的driver,在错综复杂的i2c架构中,相对于只起到了一个承上启下的作用,上接i2c控制器的结构体及driver,下接i2c设备的结构体i2c_client和特点的driver。adapter.dev.parent为i2c控制器对应的device,所以就会出现名为i2c-0/1/2的设备kobject,只是该设备的bus总线和device_type是:
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
然后调用函数device_register(&adap->dev);来注册这个device,所以在对应的i2c-0/1/2目录下也会出现power目录。
/sys/devices/platform/mt6516-i2c.2/i2c-2/power

i2c设备会通过自动检测或者事先静态描述的方式来注册进系统,不管什么方式,都会调用到函数:i2c_new_device()
struct i2c_client*client;
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
client->addr);
status = device_register(&client->dev);
可以看得出来名字是什么了,例如:2-00aa
#ls -l /sys/devices/platform/mt6516-i2c.2/i2c-2/2-00aa
-rw-r--r-- root root 4096 2010-01-02 06:35 uevent
-r--r--r-- root root 4096 2010-01-02 06:38 name
-r--r--r-- root root 4096 2010-01-02 06:38 modalias
lrwxrwxrwx root root 2010-01-02 06:38 subsystem -> ../../../../../bus/i2c
drwxr-xr-x root root 2010-01-02 06:35 power
lrwxrwxrwx root root 2010-01-02 06:38 driver -> ../../../../../bus/i2c/drivers/mt6516-tpd

三、bus_type、device_driver、device_type、class中的dev_pm_ops方法结构体
在新的linux内核中,已不再有subsystem数据结构了,他的功能被kset代替。

全局变量bus_kset初始化:kernel_init()-->do_basic_setup()-->driver_init()-->buses_init()
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);

1. 总线类型结构体:bus_type,以platform和i2c总线为例:
@kernel/drivers/base/platform.c
static const struct dev_pm_ops platform_dev_pm_ops = {
.prepare = platform_pm_prepare,//
.complete = platform_pm_complete,//
.suspend = platform_pm_suspend,//
.resume = platform_pm_resume,//
.freeze = platform_pm_freeze,
.thaw = platform_pm_thaw,
.poweroff = platform_pm_poweroff,//
.restore = platform_pm_restore,
.suspend_noirq = platform_pm_suspend_noirq,
.resume_noirq = platform_pm_resume_noirq,
.freeze_noirq = platform_pm_freeze_noirq,
.thaw_noirq = platform_pm_thaw_noirq,
.poweroff_noirq = platform_pm_poweroff_noirq,
.restore_noirq = platform_pm_restore_noirq,
.runtime_suspend = platform_pm_runtime_suspend,
.runtime_resume = platform_pm_runtime_resume,
.runtime_idle = platform_pm_runtime_idle,
};

struct bus_type platform_bus_type = {
.name= "platform",
.dev_attrs= platform_dev_attrs,
.match= platform_match,
.uevent= platform_uevent,
.pm= &platform_dev_pm_ops,
};
从上面的dev_pm_ops结构体中拿出最普遍使用的函数指针来说明一下,对于bus_type它的电源管理是如何实现的。
static int platform_pm_prepare(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;

if (drv && drv->pm && drv->pm->prepare)
ret = drv->pm->prepare(dev);

return ret;
}
static void platform_pm_complete(struct device *dev)
{
struct device_driver *drv = dev->driver;

if (drv && drv->pm && drv->pm->complete)
drv->pm->complete(dev);
}
可以看出这两个函数都最终是利用了device_driver结构体中的dev_pm_ops函数方法结构体中的对应函数指针。

////////////////////////////////////////////
static int platform_legacy_suspend(struct device *dev, pm_message_t mesg)
{
struct platform_driver *pdrv = to_platform_driver(dev->driver);
struct platform_device *pdev = to_platform_device(dev);
int ret = 0;

if (dev->driver && pdrv->suspend)
ret = pdrv->suspend(pdev, mesg);

return ret;
}

static int platform_legacy_resume(struct device *dev)
{
struct platform_driver *pdrv = to_platform_driver(dev->driver);
struct platform_device *pdev = to_platform_device(dev);
int ret = 0;

if (dev->driver && pdrv->resume)
ret = pdrv->resume(pdev);

return ret;
}
////////////////////////////////////////////
static int platform_pm_suspend(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;

if (!drv)
return 0;

if (drv->pm) {
if (drv->pm->suspend)
ret = drv->pm->suspend(dev);
} else {
ret = platform_legacy_suspend(dev, PMSG_SUSPEND);
}

return ret;
}

static int platform_pm_resume(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;

if (!drv)
return 0;

if (drv->pm) {
if (drv->pm->resume)
ret = drv->pm->resume(dev);
} else {
ret = platform_legacy_resume(dev);
}

return ret;
}


这里suspend和resume函数也是最终都是调用了device_driver结构体的dev_pm_ops方法结构体中的对应函数指针(device_driver.pm项被初始化),否则使用老式的方法:platform_legacy_suspend(dev, PMSG_SUSPEND)和platform_legacy_resume(dev)。根据这两个函数的源码可以看出。一般地,在我们的platform device的platform driver定义中,都是实现了pdrv.suspend和pdrv.resume函数,而并没有实现pdrv.driver.suspend和pdrv.driver.resume函数,其余三个函数可以在platform_driver_register()函数中看出:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;

return driver_register(&drv->driver);
}

i2c总线注册没有使用新式的电源管理方法:dev_pm_ops,仍然使用老式的方式:
@kernel/drivers/i2c/i2c-core.c
struct bus_type i2c_bus_type = {
.name= "i2c",
.match= i2c_device_match,
.probe= i2c_device_probe,
.remove= i2c_device_remove,
.shutdown= i2c_device_shutdown,
.suspend= i2c_device_suspend,
.resume= i2c_device_resume,
};

static int i2c_device_suspend(struct device *dev, pm_message_t mesg)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;

if (!client || !dev->driver)
return 0;
driver = to_i2c_driver(dev->driver);
if (!driver->suspend)
return 0;
return driver->suspend(client, mesg);
}

static int i2c_device_resume(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;

if (!client || !dev->driver)
return 0;
driver = to_i2c_driver(dev->driver);
if (!driver->resume)
return 0;
return driver->resume(client);
}
// 实际上都是调用的i2c_driver结构体的suspend和resume函数。

2. device_type结构体暂时还没有找到有哪一个模块使用了新式了dev_pm_ops电源管理方法,一般都是没有实现这部分。

3. class结构体也没有找到使用dev_pm_ops方法结构体的地方,先暂时放一放。

4. device_driver
struct device_driver {
const char*name;
struct bus_type*bus;
...
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;

const struct dev_pm_ops *pm;

struct driver_private *p;
};

struct i2c_driver {
...
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
...
struct device_driver driver;
const struct i2c_device_id *id_table;

/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
一般都是实现了platform driver和i2c_driver结构体的suspend和resume函数,并没有使用新式的电源管理方式。

  • 21:21
  • 评论 / 浏览 (0 / 374)
2011-05-29
缩略显示

标准linu休眠和唤醒机制分析(四)

MTK UP C C++ C#

suspend第三、四、五阶段:platform、processor、core

static int suspend_enter(suspend_state_t state)

{

int error;

if (suspend_ops->prepare) {

// 平台特定的函数,mtkpm.c, 有定义,对pmic和cpu dll的一些设置

error = suspend_ops->prepare();

if (error)

return error;

}

error = dpm_suspend_noirq(PMSG_SUSPEND);

// 对于一些non-sysdev devices,需要调用禁止中断的dpm_suspend函数来suspend那些设备

if (error) {

printk(KERN_ERR "PM: Some devices failed to power down\n");

goto Platfrom_finish;

}

if (suspend_ops->prepare_late) { // 这里没定义

error = suspend_ops->prepare_late();

if (error)

goto Power_up_devices;

}

if (suspend_test(TEST_PLATFORM)) // suspend第3阶段到此为止

goto Platform_wake;

error = disable_nonboot_cpus(); // disable nonboot cpus

if (error || suspend_test(TEST_CPUS)) // suspend第4阶段到此为止

goto Enable_cpus;

arch_suspend_disable_irqs(); // 中断禁止

BUG_ON(!irqs_disabled());

error = sysdev_suspend(PMSG_SUSPEND); // kernel/driver/base/sys.c

// suspend system devices

if (!error) {

if (!suspend_test(TEST_CORE)) // suspend第5阶段到此为止

error = suspend_ops->enter(state);

// 真正才进入suspend,调用的函数时平台特定的suspend enter函数, // mtkpm.c, 在下面列出mtk平台的该函数实现,供分析:

// 如果有唤醒源被操作,那么处理将会被wakeup,先做一些平台相 // 关的动作,最后从函数suspend_ops->enter()中返回,这之后的唤 // 醒操作实际上是按照suspend流程的相反顺序的来走的。

sysdev_resume(); // resuem system devices

// 跳到本文档最后面,将会有一个总结,这里会展示出正常的suspend和resume的时候函数调用

}

arch_suspend_enable_irqs();

BUG_ON(irqs_disabled());

Enable_cpus:

enable_nonboot_cpus();

Platform_wake:

if (suspend_ops->wake) // 平台无定义

suspend_ops->wake();

Power_up_devices:

dpm_resume_noirq(PMSG_RESUME);

Platfrom_finish:

if (suspend_ops->finish) // 做和函数suspend_ops->prepare()相反的工作

suspend_ops->finish();

return error;

}

static int mtk_pm_enter(suspend_state_t state)

{

_Chip_pm_enter(state);

return 0;

}

int _Chip_pm_enter(suspend_state_t state)

{

MSG_FUNC_ENTRY();

printk("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");

printk("_Chip_pm_enter @@@@@@@@@@@@@@@@@@@@@@\n");

printk(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");

/* ensure the debug is initialised (if enabled) */

switch (state)

{

case PM_SUSPEND_ON:

MSG(SUSP,"mt6516_pm_enter PM_SUSPEND_ON\n\r");

break;

case PM_SUSPEND_STANDBY:

MSG(SUSP,"mt6516_pm_enter PM_SUSPEND_STANDBY\n\r");

break;

case PM_SUSPEND_MEM: // 只支持mem的系统省电模式

MSG(SUSP,"mt6516_pm_enter PM_SUSPEND_MEM\n\r");

if (g_ChipVer == CHIP_VER_ECO_2)

mt6516_pm_SuspendEnter();

// 让cpu进入省电模式的函数,真正休眠之后,执行的代码会停在这个函数中,直到外部有EINT将其cpu唤醒,停下来的代码才继续执行,也就是正常按下了唤醒键的时候。

break;

case PM_SUSPEND_MAX:

MSG(SUSP,"mt6516_pm_enter PM_SUSPEND_MAX\n\r");

MSG(SUSP,"Not support for MT6516\n\r");

break;

default:

MSG(SUSP,"mt6516_pm_enter Error state\n\r");

break;

}

return 0;

}

void mt6516_pm_SuspendEnter(void)

{

UINT32 u32TCM = 0xF0400000;

UINT32 u4SuspendAddr = 0;

UINT32 u4Status, u4BATVol;

UINT32 counter = 0;

/* Check Chip Version*/

if (g_ChipVer == CHIP_VER_ECO_1)

u4SuspendAddr = u32TCM;

else if(g_ChipVer == CHIP_VER_ECO_2)

u4SuspendAddr = __virt_to_phys((unsigned long)MT6516_CPUSuspend);

/*wifi low power optimization : shutdown MCPLL & VSDIO */

wifi_lowpower_opt(TRUE);

/* Check PM related register*/

mt6516_pm_RegDump();

//mt6326_check_power();

DRV_WriteReg32(APMCUSYS_PDN_SET0,0x04200000);

/* STEP7: Set AP_SM_CNF(DxF003C22C) to wanted wake-up source. 设置唤醒源*/

#if defined(PLATFORM_EVB)

mt6516_pm_SetWakeSrc((1<< WS_KP)|(1<

#elif defined(PMIC_BL_SETTING)

mt6516_pm_SetWakeSrc((1<<

WS_KP)|(1<

#else

mt6516_pm_SetWakeSrc((1<

//mt6516_pm_SetWakeSrc((1<

#endif

/* Save interrupt masks*/

irqMask_L = *MT6516_IRQ_MASKL;

irqMask_H = *MT6516_IRQ_MASKH;

mt6516_pm_Maskinterrupt(); // 20100316 James

while(1)

{

#ifdef AP_MD_EINT_SHARE_DATA

/* Update Sleep flag*/

mt6516_EINT_SetMDShareInfo();

mt6516_pm_SleepWorkAround();

#endif

/* Enter suspend mode, mt6516_slpctrl.s */

if ( g_Sleep_lock <= 0 )

u4Status = MT6516_CPUSuspend (u4SuspendAddr, u32TCM);

else

MSG(SUSP,"Someone lock sleep\n\r");

#ifdef AP_MD_EINT_SHARE_DATA

mt6516_pm_SleepWorkAroundUp();

#endif

/* Check Sleep status*/

u4Status = mt6516_pm_CheckStatus();

if (u4Status == RET_WAKE_TIMEOUT)

{

#ifndef PLATFORM_EVB

DRV_WriteReg32(APMCUSYS_PDN_CLR0,0x04200000);

u4BATVol = (mt6516_pm_GetOneChannelValue(VBAT_CHANNEL,VBAT_COUNT)/VBAT_COUNT);

DRV_WriteReg32(APMCUSYS_PDN_SET0,0x04200000);

MSG(SUSP,"counter = %d, vbat = %d\n\r",counter++, u4BATVol);

if(u4BATVol <= LOW_BAT_ALARM)

{

MSG(SUSP,"Battery Low!!Power off\n\r");

bBAT_UVLO = TRUE;

goto SLP_EXIT;

}

#endif

}

else

{

MSG(SUSP,"leave sleep, wakeup!!\n\r");

goto SLP_EXIT;

//break;

}

}

SLP_EXIT:

wifi_lowpower_opt(FALSE);

/* Restore interrupt mask ; */

*MT6516_IRQ_MASKL = irqMask_L;

*MT6516_IRQ_MASKH = irqMask_H;

}

函数MT6516_CPUSuspend (u4SuspendAddr, u32TCM)是一段汇编代码,在文件:

Kernel/arch/arm/amch-mt6516/mt6516_slpctrl.S中。下面是这段汇编代码片段,看一看也蛮有意思,因为处理进入low power模式之后,是停留在该函数之中的。

ENTRY(MT6516_CPUSuspend)

stmfd sp!, {r4-r12, lr}

// r0 = MT6516_CPUSuspend physical address,

// r1 = TCM address

mov r4, r0

mov r9, r1

// Set SVC mode

mrs r0, cpsr

bic r0, r0, #MODE_MASK1

orr r1, r0, #Mode_SVC

// Set I/F bit, disable IRQ and FIQ

orr r1, r1, #I_Bit|F_Bit

// Update CPSR

msr cpsr_cxsf, r1

// calculate the physical address of instruction after disable MMU

ldr r0, =PhysicalPart

ldr r1, =MT6516_CPUSuspend

sub r0, r0, r1

mov r1, r4

// Now r0 is the physical address of PhysicalPart

add r0, r0, r1

...

...

// Power down Cache and MMU, MCU_MEM_PDN

ldr r0, =0xF0001308

ldr r1, [r0]

// ldr r1, =0xFFFFFFFF

orr r1, r1, #0x0F

str r1, [r0]

// STEP1: Set AP SLEEP (IRQ CODE: 0x36) to level sensitive on CIRQ.

// already done when system start.

// STEP2: Unmask AP SLEEP CTRL interrupt.

// already done at mt6516_pm_Maskinterrupt.

// STEP3: EOI AP SLEEP interrupt.

// already done at mt6516_pm_Maskinterrupt.

// STEP4: Read clear AP_SM_STA (OxF003C21C).

// already done at mt6516_pm_Maskinterrupt.

  • 21:13
  • 评论 / 浏览 (0 / 277)
2011-05-29
缩略显示

标准linu休眠和唤醒机制分析(三)

MTK Linux C C++ C#

五、suspend和resume代码走读

下面对suspend分的几个阶段都是按照pm test的5中模式来划分的:freezer、devices、platform、processors、core。

suspend第一阶段:freezer

int enter_state(suspend_state_t state)

{

int error;

if (!valid_state(state))

return -ENODEV;

if (!mutex_trylock(&pm_mutex)) // def in kernel/kernel/power/main.c

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();

// suspend前准备工作:Run suspend notifiers, allocate a console and stop all processes

if (error) // 如果一些准备工作失败,通常为冻结进程的时候某些进程拒绝进入冻结模式

goto Unlock; // 释放锁,然后退出

if (suspend_test(TEST_FREEZER))

// 检查上层下达的命令是否是:

// echo freezer > /sys/power/pm_test

// echo mem > /sys/power/state

// 是的话,延时5s后,然后做一些解冻进程等工作就返回

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;

}

static int suspend_prepare(void)

{

int error;

if (!suspend_ops || !suspend_ops->enter) // mtk_pm_enter() in mtkpm.c

return -EPERM;

pm_prepare_console(); //给suspend分配一个虚拟终端来输出信息

error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);

// 广播一个通知给通知链pm_chain_head,该通知链上都是用函数register_pm_notifier()注册的pm通知项(也可以用宏pm_notifier定义和注册),这里按照注册时候的定义的优先级来调用该通知链上注册的回调函数。

// PM_SUSPEND_PREPARE是事件值,表示将要进入suspend

if (error)

goto Finish;

error = usermodehelper_disable(); // 关闭用户态的helper进程

if (error)

goto Finish;

error = suspend_freeze_processes();

// 冻结所有的进程, 这里会保存所有进程当前的状态。

if (!error)

return 0;

// 也许有一些进程会拒绝进入冻结状态, 当有这样的进程存在的时候, 会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程.

suspend_thaw_processes(); // 进程解冻

usermodehelper_enable(); // enable helper process

Finish:

pm_notifier_call_chain(PM_POST_SUSPEND); // 广播退出suspend的通知

pm_restore_console();

return error;

}

// 如果不支持pm debug的话,该函数直接返回0

static int suspend_test(int level)

{

#ifdef CONFIG_PM_DEBUG

if (pm_test_level == level) {

printk(KERN_INFO "suspend debug: Waiting for 5 seconds.\n");

mdelay(5000);

return 1;

}

#endif /* !CONFIG_PM_DEBUG */

return 0;

}

static void suspend_finish(void)

{

suspend_thaw_processes();

usermodehelper_enable();

pm_notifier_call_chain(PM_POST_SUSPEND);

pm_restore_console();

}

同步文件系统函数sys_sync()调用后,将会执行函数suspend_prepare()来做一些进入suspend之前的准备工作:Run suspend notifiers, allocate a console and stop all processes,disable user mode hleper process。之后将会调用suspend_test(TEST_FREEZER)来判断上层是否有下这样的命令下来:echo freezer > /sys/power/pm_test(如果pm debug支持),如果没有下这个命令或者系统根本就不支持pm debug,那么直接返回0。如果该测试函数返回了1,那么就不会继续往下走suspend的流程了,而是调用函数suspend_finish()来结束freezer模式的pm test。返回0,将会进入第二阶段继续suspend的流程。

suspend第二阶段:devices

后续所有对suspend划分的阶段都包含在函数suspend_devices_and_enter(state)之中,所以这个函数是关键所在。

int suspend_devices_and_enter(suspend_state_t state)

{

int error;

if (!suspend_ops)

// 一个重要的函数指针结构体,特定平台的不一样,

// kernel/arch/arm/mach-mt6516/mtkpm.c

return -ENOSYS;

if (suspend_ops->begin) {

error = suspend_ops->begin(state);

// 调用特定平台实现的suspend_begin函数,

// 这里没做什么实际的工作,打印了点字符串

if (error)

goto Close; // 如果有错,执行特定平台的suspend_end函数

}

suspend_console(); // suspend console subsystem

suspend_test_start(); // suspend devices超时警告测试

error = dpm_suspend_start(PMSG_SUSPEND); // suspend all devices, 外设休眠

// 关键函数之一, kernel/drivers/base/power/main.c

if (error) {

printk(KERN_ERR "PM: Some devices failed to suspend\n");

goto Recover_platform; // 如果外设休眠过程出现错误,将会终止suspend过程,直接跳到某标签出resume外设

}

suspend_test_finish("suspend devices");

if (suspend_test(TEST_DEVICES)) // suspend第二阶段以此为界

goto Recover_platform;

suspend_enter(state); // 关键函数之一, pm test的后三种模式都在该函数中进行测试:platform、cpus、core

Resume_devices:

suspend_test_start();

dpm_resume_end(PMSG_RESUME);

suspend_test_finish("resume devices");

resume_console();

Close:

if (suspend_ops->end)

suspend_ops->end();

return error;

Recover_platform:

if (suspend_ops->recover)

suspend_ops->recover(); // 该函数目前的平台没有实现

goto Resume_devices;

}

@kernel/drivers/base/power/main.c

int dpm_suspend_start(pm_message_t state)

{

int error;

might_sleep();

error = dpm_prepare(state);

if (!error)

error = dpm_suspend(state); // 这两个函数的架构有一些类似

return error;

这两个函数如果在执行某一个device的->prepare()和->suspend回调函数的时候出错,都将会直接跳出返回错误码,不理会后续devices。

}

static int dpm_prepare(pm_message_t state)

{

struct list_head list;

int error = 0;

INIT_LIST_HEAD(&list);

mutex_lock(&dpm_list_mtx); // 锁住dpm_list链表

transition_started = true; // device_pm_add()中使用

while (!list_empty(&dpm_list)) { // dpm_list上挂着所有的devices

// 关于device中的这部分内容,参考文档:

// 新版linux系统设备架构中关于电源管理方式的变更.txt

struct device *dev = to_device(dpm_list.next); // 取得对应的device结构体

get_device(dev);

dev->power.status = DPM_PREPARING;

// 将该设备的电源状态设置成DPM_PREPARING,表示该设备正准备着进入相应的省电模式,这之后就会调用和该设备有关的所以的-->prepare函数

mutex_unlock(&dpm_list_mtx);

pm_runtime_get_noresume(dev);

// 电源管理新方式中比较复杂的用法,这里没有使用到,所以直接跳过

if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) {

/* Wake-up requested during system sleep transition. */

pm_runtime_put_noidle(dev);

error = -EBUSY;

} else {

error = device_prepare(dev, state);

// 这个才是真正执行-->prepare回调函数的地方

}

mutex_lock(&dpm_list_mtx);

if (error) {

dev->power.status = DPM_ON;

if (error == -EAGAIN) { // try again

put_device(dev);

error = 0;

continue;

}

printk(KERN_ERR "PM: Failed to prepare device %s "

"for power transition: error %d\n",

kobject_name(&dev->kobj), error);

put_device(dev);

break;

}

dev->power.status = DPM_SUSPENDING;

// 这之后就不能以它为父设备再注册设备了,可以从函数device_pm_add

// 中看出来

if (!list_empty(&dev->power.entry))

list_move_tail(&dev->power.entry, &list);

// 为了该函数中循环链表之用, list_empty(&dpm_list)

put_device(dev);

}

list_splice(&list, &dpm_list);

mutex_unlock(&dpm_list_mtx);

return error;

}

从这个函数我们可以发现,每一个device的dev->power.status状态都会有如下轨迹的变化:DPM_ON(device刚刚注册完的初始状态) --> DPM_PREPARING --> DPM_SUSPENDING --> 未完待续...

static int device_prepare(struct device *dev, pm_message_t state)

{

int error = 0;

down(&dev->sem);

// dev->bus绝大多数都存在,dev->bus->pm这个就不一定了,不过platform bus一定存在,而i2c bus就没有,dev->bus->pm->prepare这个函数如果存在就会被后面给调用到,以platform bus为例,该函数的实现完全回去调用device对应的driver->pm->prepare()函数(如果存在的话),当然driver->pm->prepare()不存在就什么也不做了 。

if (dev->bus && dev->bus->pm && dev->bus->pm->prepare) {

pm_dev_dbg(dev, state, "preparing ");

error = dev->bus->pm->prepare(dev);

suspend_report_result(dev->bus->pm->prepare, error);

if (error)

goto End;

}

// dev->type这个很多设备都有,但是dev->type->pm这个却很少有了

if (dev->type && dev->type->pm && dev->type->pm->prepare) {

pm_dev_dbg(dev, state, "preparing type ");

error = dev->type->pm->prepare(dev);

suspend_report_result(dev->type->pm->prepare, error);

if (error)

goto End;

}

// dev->class很少有设备有

if (dev->class && dev->class->pm && dev->class->pm->prepare) {

pm_dev_dbg(dev, state, "preparing class ");

error = dev->class->pm

  • 21:08
  • 评论 / 浏览 (0 / 642)
2011-05-29
缩略显示

标准linu休眠和唤醒机制分析(二)

MTK Linux Android Hibernate C

三、pm_test属性文件读写

int pm_test_level = TEST_NONE;

static const char * const pm_tests[__TEST_AFTER_LAST] = {

[TEST_NONE] = "none",

[TEST_CORE] = "core",

[TEST_CPUS] = "processors",

[TEST_PLATFORM] = "platform",

[TEST_DEVICES] = "devices",

[TEST_FREEZER] = "freezer",

};

// core >> processors >> platform >> devices >> freezer, 控制范围示意

cat pm_test的时候最终会调用函数pm_test_show(),在终端上打印出上面数组中的字符串,当前的模式用[]表示出来。

echo devices > pm_test的时候会最终调用到函数pm_test_store()中去,该函数中设置全局变量pm_test_level的值,可以是0-5,分别代表上none ~ freezer。该全局变量会在后面的suspend和resume中被引用到。

memchr函数说明:

原型:extern void *memchr(void *buf, char ch, unsigned int count);

用法:#include   

功能:从buf所指内存区域的前count个字节查找字符ch。   

说明:当第一次遇到字符ch时停止查找。如果成功,返回指向字符ch的指针;否则返回NULL。

四、state属性文件

power_attr(state)宏定义了一个struct kobj_attribute结构体state_attr:

static struct kobj_attribute state_attr = {

.attr = {

.name = __stringify(state),

.mode = 0644,

},

.show = state_show,

.store = state_store,

}

kobj_attribute结构体封装了struct attribute结构体,新建属性文件是依据struct attribute结构体。最终通过函数kobj_attr_show和kobj_attr_store回调到实际的show和store函数(kobject.c)。

state_show()函数主要是显示当前系统支持哪几种省电模式。

static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)

{

char *s = buf;

#ifdef CONFIG_SUSPEND //def

int i;

for (i = 0; i < PM_SUSPEND_MAX; i++) {

if (pm_states[i] && valid_state(i))

s += sprintf(s,"%s ", pm_states[i]);

}

#endif

#ifdef CONFIG_HIBERNATION // undef, don't support STD mode

s += sprintf(s, "%s\n", "disk");

#else

if (s != buf)

/* convert the last space to a newline */

*(s-1) = '\n';

#endif

return (s - buf);

}

@ kernel/include/linux/suspend.h

#define PM_SUSPEND_ON ((__force suspend_state_t) 0)

#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 1)

#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)

#define PM_SUSPEND_DISK ((__force suspend_state_t) 4)

#define PM_SUSPEND_MAX ((__force suspend_state_t) 5)

@ kernel/kernel/power/suspend.c

const char *const pm_states[PM_SUSPEND_MAX] = {

#ifdef CONFIG_EARLYSUSPEND // android修改了标准linux的休眠唤醒机制,增加了eraly suspend和late resume机制,如果是android内核,则这个宏是需要定义的。

[PM_SUSPEND_ON] = "on",

#endif

[PM_SUSPEND_STANDBY] = "standby",

[PM_SUSPEND_MEM] = "mem",

};

该函数中值得注意的地方应该是valid_state(i),这个函数是用户配置的支持省电模式的验证函数,如果没有这个验证过程,cat时候打印出来的模式则是on standby mem,给上层用户的使用造成困扰。

那这个valid_state()函数在哪里定义的呢?一般定义于文件kernel/kernel/power/suspend.c

static struct platform_suspend_ops *suspend_ops;

void suspend_set_ops(struct platform_suspend_ops *ops) // 该函数调用见后面

{

mutex_lock(&pm_mutex);

suspend_ops = ops;

mutex_unlock(&pm_mutex);

}

bool valid_state(suspend_state_t state)

{

return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);

}

而实际平台的platform_suspend_ops结构体一般都是在文件arch/arm/mach-xxxx/pm.c中进行定义,对于mtk的平台是文件mtkpm.c,如下:

@ kernel/include/linux/suspend.h

struct platform_suspend_ops {

int (*valid)(suspend_state_t state);

int (*begin)(suspend_state_t state);

int (*prepare)(void);

int (*prepare_late)(void);

int (*enter)(suspend_state_t state);

void (*wake)(void);

void (*finish)(void);

void (*end)(void);

void (*recover)(void);

};

经过后面的代码分析,得出了如下结论:

休眠唤醒过程依次会执行的函数是:begin,prepare,prepare_late,enter,wake, finish, end。同颜色的函数执行了恰好相反的工作。休眠的时候代码执行是停留在函数enter中,wake之后也是从suspend的时候停留的地方继续运行。

至于recover函数貌似只有在pm_test处于devices的模式下,才会被调用到。

@ kernel/arch/arm/mach-mt6516/mtkpm.c

static struct platform_suspend_ops mtk_pm_ops = {

.valid = mtk_pm_state_valid,

.begin = mtk_pm_begin,

.prepare = mtk_pm_prepare,

.enter = mtk_pm_enter,

.finish = mtk_pm_finish,

.end = mtk_pm_end,

};

static int mtk_pm_state_valid(suspend_state_t pm_state)

{

return pm_state == PM_SUSPEND_MEM ;

}

void mtk_pm_init(void)

{

_Chip_PM_init();

/* Register and set suspend operation */

suspend_set_ops(&mtk_pm_ops);

}

而函数mtk_pm_init()是在函数mt6516_init_irq()中调用。可以看出该平台只支持mem的省电模式。

state_store()函数:

static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,

const char *buf, size_t n)

{

#ifdef CONFIG_SUSPEND // set

#ifdef CONFIG_EARLYSUSPEND //对标准linux而言,这个宏不存在

suspend_state_t state = PM_SUSPEND_ON;

#else

suspend_state_t state = PM_SUSPEND_STANDBY;

#endif

const char * const *s;

#endif

char *p;

int len;

int error = -EINVAL;

p = memchr(buf, '\n', n);

len = p ? p - buf : n;

/* First, check if we are requested to hibernate */

if (len == 4 && !strncmp(buf, "disk", len)) {

error = hibernate(); // 如果值是disk,那么进入STD模式,该模式暂不讨论

goto Exit;

}

#ifdef CONFIG_SUSPEND // def

for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {

if (*s && len == strlen(*s) && !strncmp(buf, *s, len))

break;

}

if (state < PM_SUSPEND_MAX && *s)

#ifdef CONFIG_EARLYSUSPEND

// android的linux内核会定义该宏,首先进入eraly suspend模式

if (state == PM_SUSPEND_ON || valid_state(state)) {

error = 0;

request_suspend_state(state);

}

#else // 标准linux内核直接enter_state()函数

error = enter_state(state); // kernel/kernel/power/suspend.c

#endif

#endif

Exit:

return error ? error : n;

}

  • 21:04
  • 评论 / 浏览 (0 / 497)
2011-05-29
缩略显示

标准linu休眠和唤醒机制分析(一)

Linux 配置管理 Android 数据结构 项目管理

说明:

1. Based on linux2.6.32, only for mem(SDR)

2. 有兴趣请先参考阅读: 电源管理方案APM和ACPI比较.doc

Linux系统的休眠与唤醒简介.doc

3. 本文先研究标准linux的休眠与唤醒,android对这部分的增改在另一篇文章中讨论

4. 基于手上的一个项目来讨论,这里只讨论共性的地方

虽然linux支持三种省电模式:standby、suspend to ram、suspend to disk,但是在使用电池供电的手持设备上,几乎所有的方案都只支持STR模式(也有同时支持standby模式的),因为STD模式需要有交换分区的支持,但是像手机类的嵌入式设备,他们普遍使用nand来存储数据和代码,而且其上使用的文件系统yaffs一般都没有划分交换分区,所以手机类设备上的linux都没有支持STD省电模式。

一、项目power相关的配置

目前我手上的项目的linux电源管理方案配置如下,.config文件的截图,当然也可以通过make menuconfig使用图形化来配置:

#

# CPU Power Management

#

# CONFIG_CPU_IDLE is not set

#

# Power management options

#

CONFIG_PM=y

# CONFIG_PM_DEBUG is not set

CONFIG_PM_SLEEP=y

CONFIG_SUSPEND=y

CONFIG_SUSPEND_FREEZER=y

CONFIG_HAS_WAKELOCK=y

CONFIG_HAS_EARLYSUSPEND=y

CONFIG_WAKELOCK=y

CONFIG_WAKELOCK_STAT=y

CONFIG_USER_WAKELOCK=y

CONFIG_EARLYSUSPEND=y

# CONFIG_NO_USER_SPACE_SCREEN_ACCESS_CONTROL is not set

# CONFIG_CONSOLE_EARLYSUSPEND is not set

CONFIG_FB_EARLYSUSPEND=y

# CONFIG_APM_EMULATION is not set

# CONFIG_PM_RUNTIME is not set

CONFIG_ARCH_SUSPEND_POSSIBLE=y

CONFIG_NET=y

上面的配置对应下图中的下半部分图形化配置。。。,看来是直接在Kconfig文件中删除了配置STD模式的选项。

使用上面的配置编译出来的系统,跑起来之后,进入sys目录可以看到相关的接口:

# pwd

/sys/power

# ls

state wake_lock wake_unlock wait_for_fb_sleep wait_for_fb_wake

# cat state

mem

如果配置了宏CONFIG_PM_DEBUG,那么在power目录下会多出一个pm_test文件,cat pm_test后,列出的测试选项有:[none] core processors platform devices freezer。关于这个test模式的使用,可以参考kernel文档:/kernel/documentation/power/Basic-pm-debugging.txt

这个文档我也有详细的阅读和分析。

二、sys/power和相关属性文件创建

系统bootup时候在sys下新建power和相关属性文件,相关源码位置:

kernel/kernel/power/main.c

static int __init pm_init(void)

{

int error = pm_start_workqueue();// CONFIG_PM_RUNTIME not set, so this fun is null

if (error)

return error;

power_kobj = kobject_create_and_add("power", NULL);

// 建立power对应的kobject和sysfs_dirent对象,同时建立联系:kobject.sd =

// &sysfs_dirent, sysfs_dirent.s_dir->kobj = &kobject。

if (!power_kobj)

return -ENOMEM;

return sysfs_create_group(power_kobj, &attr_group);

// 建立一组属性文件,可以在power下建立一个子目录来存放这些属性文件, // 不过需要在结构体attr_group中指定name,否则直接将这些属性文件放在 // power_kobj对应的目录下。

}

core_initcall(pm_init); // 看的出来,该函数是很早就被调用,initcall等级为1

static struct attribute_group attr_group = {

.attrs = g,

};

struct attribute_group {

const char *name;

mode_t (*is_visible)(struct kobject *,

struct attribute *, int);

struct attribute **attrs;

};

// 属性文件都是以最基本得属性结构struct attribute来建立的

static struct attribute * g[] = {

&state_attr.attr,

#ifdef CONFIG_PM_TRACE // not set

&pm_trace_attr.attr,

#endif

#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_DEBUG) // not set

&pm_test_attr.attr,

#endif

#ifdef CONFIG_USER_WAKELOCK // set

&wake_lock_attr.attr,

&wake_unlock_attr.attr,

#endif

NULL,

};

#ifdef CONFIG_PM_SLEEP

#ifdef CONFIG_PM_DEBUG

power_attr(pm_test);

#endif

#endif

power_attr(state);

#ifdef CONFIG_PM_TRACE

power_attr(pm_trace);

#endif

#ifdef CONFIG_USER_WAKELOCK

power_attr(wake_lock);

power_attr(wake_unlock);

#endif

#define power_attr(_name) \

static struct kobj_attribute _name##_attr = { \

.attr = { \

.name = __stringify(_name), \

.mode = 0644, \

}, \

.show = _name##_show, \

.store = _name##_store, \

}

// 而这些被封装过的属性结构体,将来会使用kobject的ktype.sysfs_ops->show(store)这两个通用函数通过container_of()宏找到实际的属性结构体中的show和store函数来调用。

关于更多sysfs的内容,请查看其他关于这部分内容的详细解析文档。


你可能感兴趣的:(linux驱动学习,android学习)