最近研究marvell平台上的PMU管理机制,算是边学边写吧。 自己是个菜鸟,可能写起来就信马由缰了,毕竟懂得东西实在太少了。
marvell主要PMU管理在pxa3xx_pm.c中,而很多函数是建立在kernel/power目录下的main.c上的。下面先分析main.c文件吧
首先当然是注册了:
static int __init pm_init(void);
这个函数使用了core_initcall(pm_init); 这个就涉及到linux driver启动顺序的问题。 core_initcall确保可以在比较靠前的顺序进行注册,防止了后面如果有用到这个驱动里的函数,但是此时此驱动还没有注册。(据说如果默认使用module_init,注册顺序是不固定的)。
在pm_init里注册了subsystem,
subsystem_register(&power_subsys);
当然在进行注册前,先声明了变量power_subsys,使用方法是 decl_subsys(power,NULL,NULL);
可以对应源代码,看看decl_subsys到底干了啥事情,说白了就是初始化呗,声明并初始化了一个叫power_subsys的变量。
##在linux还是蛮常用的嘛。
#define decl_subsys(_name,_type,_uevent_ops) \
struct kset _name##_subsys = { \
.kobj = { .name = __stringify(_name) }, \
.ktype = _type, \
.uevent_ops =_uevent_ops, \
}
Ok,子系统注册完了,该在sys下生成文件了。可以使用sysfs_create_group,在指定目录生成sys文件了,刚开始还很奇怪为啥使用sysfs_create_group,而不是使用sysfs_create_file,后来发现作者为了便于调试,通过宏定义,定义了一个trace的sysfs。这样,使用group函数可以在该目录同时生成两个,不用一步一步分别create了。代码如下:
#ifdef CONFIG_PM_TRACE
。。。。。。
static struct attribute * g[] = {
&state_attr.attr,
&pm_trace_attr.attr,
NULL,
};
#else
static struct attribute * g[] = {
&state_attr.attr,
NULL,
};
#endif /* CONFIG_PM_TRACE */
static struct attribute_group attr_group = {
.attrs = g,
};
Ok,到这里init就完成了。还算是比较简单。但是linux的作者们各种技巧确实给俺留下了深刻的印象。
例如sysfs的operation的声明,已经不止一次看到使用一个宏定义进行sysfs的operation声明了。
power_attr(pm_trace); //这样就声明了一个叫做pm_trace_attr的sysfs操作,attr为pm_trace。
欣赏一下它的原型吧:
#define power_attr(_name) \
static struct subsys_attribute _name##_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0644, \
}, \
.show = _name##_show, \
.store = _name##_store, \
}
很帅,至少我认为是这样。特别是有2个以上的sysfs文件是。 不过带来的坏处就是使用source insight时,经常不知所踪。唉。
pm_trace其实是使用sysfs来控制trace的显示,不过看代码,没有看到它控制哪里的trace啊,奇怪。
学学sysfs的show和store。嘿嘿,跑题了,不过谁让俺水平就那么低了,经过坎坷的找工作,刚开始入手linux。
static ssize_t pm_trace_show(struct subsystem * subsys, char * buf)
显示文件里的内容,直接使用sprintf就可以啦。 sprintf(buf, "%d\n", pm_trace_enabled);
static ssize_t pm_trace_store(struct subsystem * subsys, const char * buf, size_t n)
当往文件里写入时,执行该函数。 sscanf(buf, "%d", &val), 然后pm_trace_enabled = !!val;哈哈。结束了。多么简单。
其实proc文件系统也蛮简单的,以后阅读到的时候再分析吧。
还有有必要看看state的show和store吧,我们可以通过写state文件,来进入不同的PM状态,如standby, mem, lcdfresh, deepsleep等。下面看看咋实现的:
static ssize_t state_show(struct subsystem * subsys, char * buf)
s += sprintf(s,"%s ", pm_states[i]);
。。。。。
s += sprintf(s,"\n");
在state中,将这几种状态都打印出来。
static const char * const pm_states[PM_SUSPEND_MAX] = {
[PM_SUSPEND_STANDBY] = "standby",
[PM_SUSPEND_MEM] = "mem",
#ifdef CONFIG_SOFTWARE_SUSPEND
[PM_SUSPEND_DISK] = "disk",
#endif
[PM_SUSPEND_LCDREFRESH] = "lcdrefresh",
[PM_SUSPEND_DEEPSLEEP] = "deepsleep",
NULL,
};
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
注意__force的用法。:)
static ssize_t state_store(struct subsystem * subsys, const char * buf, size_t n)
如果向state写入standby,则会进入standby模式。
p = memchr(buf, '\n', n);
len = p ? p - buf : n;
剥离回车换行符。memchr函数是在指定的n字节内查找'\n'字符,并返回所在位置。
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
if (*s && !strncmp(buf, *s, len))
break;
}
查找匹配的状态。
if (state < PM_SUSPEND_MAX && *s)
error = enter_state(state);
进入指定状态。 enter_state()。。。。。
唉,这才进入main.c文件的核心部分 enter_state。
这才到了最关键的地方static int enter_state(suspend_state_t state)
enter_state 是负责处理进入何种状态的函数。貌似在arm linux里,电源管理都经过如下几步:
1. suspend_prepare
2. suspend_enter
3. suspend_finish
当然这几个函数又有封装,但是从总体上看,上层实现时,operation也对应这几个函数。
先看看suspend_prepare的实现吧
pm_prepare_console(); 设置console为suspend模式
freeze_processes() 将user space和kernel的thread freeze
global_page_state() 貌似要free一些space出来,不是很确定呃。
pm_ops->prepare(state) 调用上层operation的prepare函数
suspend_console(); 获取console信号量,设置suspend标志位,标志进入suspend,与其对应的resume_console
device_suspend(PMSG_SUSPEND); 每个设备进入suspend模式,有待进一步分析内部实现。
disable_nonboot_cpus(); 对于我们这个单CPU,没啥用处
suspend prepare 是为了suspend做准备的一部,下一步就要进入suspend状态了。
suspend_enter实现要简单多了
device_power_down(PMSG_SUSPEND) 关闭所有device的power,如果失败,则退出,并且suspend也失败。
pm_ops->enter(state); 进入suspend模式。
device_power_up(); 退出suspend模式,将device的状态恢复
suspend_finish实现,主要恢复suspend prepare里的工作。
enable_nonboot_cpus();
pm_finish(state);
device_resume();
resume_console();
thaw_processes();
pm_restore_console();