Search This Blog

Mar 9, 2011

Set Linux power state as "mem" not working

1.客户反馈执行命令 “echo mem > /sys/power/state” 不起作用。

2. 重现和比较:

2.1 我拿客户的板子和程序,编译后跑,可以重现客户说的问题。串口显示
PM: Syncing filesystems ... done.
Freezing user space processes ... (elapsed 0.01 seconds) done.
Freezing remaining freezable tasks ... (elapsed 0.01 seconds) done.
Suspending console(s) (use no_console_suspend to debug)
就不再往下跑了,不过按键盘串口仍然有回显。
2.2 在Linux PC上执行了下“echo mem > /sys/power/state”,PC立即屏幕变黑,按键和鼠标都无反应,按机箱上的Power开关,Linux苏醒过来。
2.3 拿MX53 EVK跑标准程序,执行“echo mem > /sys/power/state”,立即进入suspend,串口无回显,按板子上的Power on键,Linux苏醒过来。

3. 定位最后显示的错误代码

从“Suspending console(s) (use no_console_suspend to debug)”,找到其在文件kernel/printk.c中函数void suspend_console()里面。
void suspend_console(void)
{
 if (!console_suspend_enabled)
  return;
 printk("Suspending console(s) (use no_console_suspend to debug)\n");
 acquire_console_sem();
 console_suspended = 1;
 up(&console_sem);
}
看到一个变量console_suspend_enabled来控制是否使串口进入suspend,往上看一下,这个变量在函数console_suspend_disable()做了赋值,
static int __init console_suspend_disable(char *str)
{
 console_suspend_enabled = 0;
 return 1;
}
__setup("no_console_suspend", console_suspend_disable);
console_suspend_disable()是 _setup()执行的函数,也就是根据u-boot传递过来的启动参数里查看是否定义了“no_console_suspend”,有定义就执行console_suspend_disable()。所有的_setup()圈定的函数会在init/main.c里面的static int __init obsolete_checksetup(char *line)集中执行。
对于调试来说,在函数里面修改比在u-boot参数休改要快捷,于是添加了一行代码在suspend_console()函数里面。
void suspend_console(void)
{
        console_suspend_enabled = 0;
 if (!console_suspend_enabled)
  return;
 printk("Suspending console(s) (use no_console_suspend to debug)\n");
 acquire_console_sem();
 console_suspended = 1;
 up(&console_sem);
}
编译运行后,执行“echo mem > /sys/power/state”打出了大量信息,一个空指针导致kernel崩溃。
root@freescale ~$ echo mem >/sys/power/state
PM: Syncing filesystems ... done.
Freezing user space processes ... (elapsed 0.01 seconds) done.
Freezing remaining freezable tasks ... (elapsed 0.01 seconds) done.
suspend_devices_and_enter
da9052_tsi_suspend: called
PM: suspend of devices complete after 7.109 msecs
mx5_suspend_prepare
suspend wp cpu=400000000
Unable to handle kernel NULL pointer dereference at virtual address 00000008
pgd = 9537c000
[00000008] *pgd=85366031, *pte=00000000, *ppte=00000000
Internal error: Oops: 17 [#1] PREEMPT
last sysfs file: /sys/power/state
Modules linked in:
CPU: 0    Not tainted  (2.6.35.3-744-g27fdf7b-g4ab9a2c-dirty #154)
PC is at set_cpu_freq+0x38/0xe8
LR is at _clk_arm_get_rate+0x20/0x28
pc : [<800541bc>]    lr : [<8003fb74>]    psr: 80000013
sp : 954d7eb8  ip : 00000100  fp : 00000002
r10: 8076fd4a  r9 : 80419028  r8 : 80401bfc
r7 : 00000001  r6 : 3b9aca00  r5 : 17d78400  r4 : 00000000
r3 : 00000001  r2 : 00000000  r1 : 00000004  r0 : 3b9aca00
Flags: Nzcv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 10c5387d  Table: 8537c019  DAC: 00000015
Process sh (pid: 2142, stack limit = 0x954d62e8)
Stack: (0x954d7eb8 to 0x954d8000)
7ea0:                                                       954d7ed0 00000000
7ec0: 8080dde8 00000001 80401bfc 8003c94c 00000000 000f4240 00061a80 003fff00
7ee0: 00000004 00000003 00000000 003fffff 00000004 8008d704 8076fd4a 00000003
7f00: 00000000 8008d930 95364000 00000003 00000003 8008cfd0 950195a0 95454718
7f20: 954d7f80 00000004 95454700 80419028 95002840 801e54d4 950195a0 8010b06c
7f40: 00000004 95404900 2aac9000 954d7f80 00000004 954d6000 0009a26c 800c0ea0
7f60: 95404900 2aac9000 95404900 2aac9000 00000000 00000000 00000004 800c0ff4
7f80: 00000000 00000000 00000004 00000000 00000004 2abf65c8 00000004 00000004
7fa0: 80033104 80032f80 00000004 2abf65c8 00000001 2aac9000 00000004 00000000
7fc0: 00000004 2abf65c8 00000004 00000004 2aac9000 00000000 0009a26c 00000002
7fe0: 00000000 7ed065e0 2ab33c48 2ab8624c 60000010 00000001 70b4a021 70b4a421
[<800541bc>] (set_cpu_freq+0x38/0xe8) from [<8003c94c>] (mx5_suspend_prepare+0xfc/0x13c)
[<8003c94c>] (mx5_suspend_prepare+0xfc/0x13c) from [<8008d704>] (suspend_devices_and_enter+0x94/0x200)
[<8008d704>] (suspend_devices_and_enter+0x94/0x200) from [<8008d930>] (enter_state+0xc0/0x124)
[<8008d930>] (enter_state+0xc0/0x124) from [<8008cfd0>] (state_store+0xa0/0xbc)
[<8008cfd0>] (state_store+0xa0/0xbc) from [<801e54d4>] (kobj_attr_store+0x18/0x1c)
[<801e54d4>] (kobj_attr_store+0x18/0x1c) from [<8010b06c>] (sysfs_write_file+0x108/0x13c)
[<8010b06c>] (sysfs_write_file+0x108/0x13c) from [<800c0ea0>] (vfs_write+0xac/0x154)
[<800c0ea0>] (vfs_write+0xac/0x154) from [<800c0ff4>] (sys_write+0x3c/0x68)
[<800c0ff4>] (sys_write+0x3c/0x68) from [<80032f80>] (ret_fast_syscall+0x0/0x30)
Code: e594203c e1a04003 e5911000 ea000003 (e5920008)
---[ end trace 53b6c4297f475235 ]---

4. 定位空指针

从上面的信息看崩溃的地址在“set_cpu_freq+0x38/0xe8”,本想用工具辅助定位在vmlinux中的具体位置,不过命令都忘了...花了五分钟回忆没有任何结果就改用printk()来定位了,set_cpu_freq这个函数也不大,应该会比较快。函数在arch/arm/plat-mxc/cpufreq.c里面。里面的各个变量,函数都看起来都老实本分,不像是NULL指针应该具有的样子。通常这些冷僻函数久经考验,基本发布后就不会有人修改的。然而现实和理想总是有一定的差距,经过定位cpu_wp_tbl的地址竟然是“0”!
int set_cpu_freq(int freq)
{
...
 pr_info("cpu_wp_tbl addr = 0x%x\n",(unsigned int)cpu_wp_tbl);
 for (i = 0; i < cpu_wp_nr; i++) {
  if (freq == cpu_wp_tbl[i].cpu_rate)
   gp_volt = cpu_wp_tbl[i].cpu_voltage;
 }
...
 return ret;
}

5. 查找cpu_wp_tbl为何为0.

cpu_wp_tbl是什么?经查证,是指CPU working points,保存cpu频率,电压等信息。在cpufreq.c里面函数有对其赋值。
static int __init mxc_cpufreq_driver_init(struct cpufreq_policy *policy)
{
...
  cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr);
 cpu_freq_khz_min = cpu_wp_tbl[0].cpu_rate / 1000;
 cpu_freq_khz_max = cpu_wp_tbl[0].cpu_rate / 1000;
...
}
而get_cpu_wp()是一个指针函数struct cpu_wp *(*get_cpu_wp)(int *wp),在arch/arm/mach-mx5/mx53_wp.c我们找到其赋值的地方.
void mx53_set_cpu_part_number(enum mx53_cpu_part_number part_num)
{
 get_cpu_wp = mx53_get_cpu_wp;
 set_num_cpu_wp = mx53_set_num_cpu_wp;
 switch (part_num) {
 case IMX53_CEC_1_2G:
  cpu_wp_table = cpu_wp_ces_1_2g;
  num_cpu_wp = ARRAY_SIZE(cpu_wp_ces_1_2g);
  break;
 case IMX53_CEC:
  cpu_wp_table = cpu_wp_ces;
  num_cpu_wp = ARRAY_SIZE(cpu_wp_ces);
  break;
 case IMX53_AEC:
 default:
  cpu_wp_table = cpu_wp_aec;
  num_cpu_wp = ARRAY_SIZE(cpu_wp_aec);
  break;
 }
}
那么mx53_get_cpu_wp又做了什么?原来只是返回一个结构数组指针。
struct cpu_wp *mx53_get_cpu_wp(int *wp)
{
 *wp = num_cpu_wp;
 return cpu_wp_table;
}
根据mx53_set_cpu_part_number()函数的内容,cpu_wp_table只能是cpu_wp_ces_1_2g,cpu_wp_ces,cpu_wp_aec。经确认,我们目前跑1GHz,也就是说cpu_wp_table = cpu_wp_ces.看起来逻辑清楚,cpu_wp_tbl怎么会为“0”呢?还是回到cpufreq.c里面的cpu_wp_tbl的赋值部分,看看它拿到一个有效的地址没有。在函数里面加上打印代码:
static int __init mxc_cpufreq_driver_init(struct cpufreq_policy *policy)
{
 int ret;
 int i;

 printk(KERN_INFO "i.MXC CPU frequency driver\n");

 if (policy->cpu != 0)
  return -EINVAL;

 cpu_clk = clk_get(NULL, "cpu_clk");
 if (IS_ERR(cpu_clk)) {
  printk(KERN_ERR "%s: failed to get cpu clock\n", __func__);
  return PTR_ERR(cpu_clk);
 }

 gp_regulator = regulator_get(NULL, gp_reg_id);
 if (IS_ERR(gp_regulator)) {
  clk_put(cpu_clk);
  printk(KERN_ERR "%s: failed to get gp regulator\n", __func__);
  return PTR_ERR(gp_regulator);
 }

 /* Set the current working point. */
 if(get_cpu_wp)
  printk(KERN_INFO"We have get_cpu_wp define!!!!!!!!!!!!!!!!!!!!!!!!!\n");
 else
  printk(KERN_INFO"We have not get_cpu_wp define!!!!!!!!!!!!!!!!!!!!!!!!!\n");
 cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr);
  printk(KERN_INFO"cpu_wp_tbl adddddr= 0x%x\n",(unsigned int)cpu_wp_tbl);

  cpu_freq_khz_min = cpu_wp_tbl[0].cpu_rate / 1000;
 cpu_freq_khz_max = cpu_wp_tbl[0].cpu_rate / 1000;
...
}
编译运行没有跑到加的输出信息那里,也就是说cpu_wp_tbl没有赋值!
仔细查看了启动的输出信息后发现有这样的输出信息:
i.MXC CPU frequency driver
regulator: get() with no identifier
mxc_cpufreq_driver_init: failed to get gp regulator
哦,在cpu_wp_tbl前面出错返回了,没有赋值,所以cpu_wp_tbl为NULL。

6. 为何regulator_get(NULL, gp_reg_id)出错?

这也是一段忠厚老实的代码,为什么总在忠厚老实的代码里面打转?不想质疑regulator_get()可靠性,直接查gp_reg_id。发现其在arch/arm/mach-mx5/bus_freq.c里面定义的,赋值也是在函数busfreq_probe()里面实现的。这里再次强调,一个好的IDE会使查找过程变得非常快速,并有效防止个人在漫长查找过程中思想溜号跑去上网等影响效率的事情发生。
static int __devinit busfreq_probe(struct platform_device *pdev)
{
...
 gp_reg_id = p_bus_freq_data->gp_reg_id;
...
}
恩,再找p_bus_freq_data->gp_reg_id,在arch/arm/mach-mx5/mx53_smd.c发现了定义。
static struct mxc_bus_freq_platform_data bus_freq_data = {
 .gp_reg_id = "DA9052_BUCK_CORE",
 .lp_reg_id = "DA9052_BUCK_PRO",
};
再看发现其竟然没有注册...
// mxc_register_device(&busfreq_device, &bus_freq_data);
迅速打开编译,运行,启动后串口输出没有再打印“mxc_cpufreq_driver_init: failed to get gp regulator”。

7. 验证

在串口键入“echo mem > /sys/power/state”,板子迅速进入suspend,按power on key,板子苏醒。OK,All done!

Mar 3, 2011

存理克欲(四)

孟子見梁惠王。王曰:「叟,不遠千里而來,亦將有以利吾國乎?」
孟子對曰:「王何必曰利?亦有仁義而已矣。」「王曰:『何以利吾國?』大夫曰:『何以利吾家?』士庶人曰:『何以利吾身?』上下交征利,而國危矣!萬乘之國,弒其君者,必千乘之家;千乘之國,弒其君者,必百乘之家。萬取千焉,千取百焉,不為不多矣;茍為後義而先利,不奪不饜。」
「未有仁而遺其親者也,未有義而後其君者也。」
「王亦曰:仁義而已矣,何必曰利?」


语译:
  孟子见梁惠王。梁惠王说:“老先生,您不远千里来到我国,将有些策略来使我国获利吧?”孟子答道:“大王何必说利呢?也只有讲仁义就是了。大王说:如何使我国获利?大夫说:如何是我家获利?士,庶人说:如何使我个人获利?假使上上下下的人都相互夺取利益,那么邦国就危险了。拥有万乘兵车的邦国,弑杀他的国君的,必是拥有千乘兵车的大夫之家;拥有千乘兵车的邦国,弑杀他的国君的,必是拥有百乘兵车的大夫之家。在万乘之国中拥有千乘兵车,在千乘之国中拥有百乘兵车,不能说不多了。假使把义放在后面,却把利放在前头,那么不全部夺走是不会满足的。没有一个有仁德的人却遗弃他的父母啊!没有一个讲道义的人却把他的国君阁在脑后啊!大王也只有讲仁义就是了,何必说利呢?

析论
   颇有感触“苟为后义而先利,不夺不餍(yan)”凡事利字当前,真是欲壑难填,无所不为。私利是祸乱相寻的源头。董仲舒说:凡事只要合乎仁义,以仁德和天地万物相感通,以道义节制其轻重厚薄,使其和于中庸之道,自然无需先考虑是否得利,而天下之大利自然就在之中了。行中道,去私利,无需瞻前顾后,处事自然顺畅无滞。

存理克欲(三)

「齊人有一妻一妾而處室者。其良人出,則必饜酒肉而後反。其妻問所與飲食者,則盡富貴也。其妻告其妾曰:『良人出,則必饜酒肉而後反,問其所與飲食者,盡富貴也,而未嘗有顯者來。將瞷良人之所之也。』蚤起,施從良人之所之,〔彳扁〕國中無與立談者,卒之東郭墦間之祭者,乞其餘,不足,又顧而之他,此其為饜足之道也。
其妻歸,告其妾曰:『良人者,所仰望而終身也。今若此!』與其妾訕其良人,而相泣於中庭。而良人未之知也,施施從外來,驕其妻妾。
由君子觀之,則人之所以求富貴利達者,其妻妾不羞也,而不相泣者,幾希矣!


语译
  齐国有一妻一妾住在一起的家庭,这户人家的丈夫一出门,就必定酒足饭饱才回家。他妻子问和他一起吃喝的是什么人,他就说都是些富贵的人。他妻子跟他的妾说:“丈夫一出门,就必定喝足酒,吃饱肉才回家。问他一起吃喝的是什么人,他就说都是些富贵的人,可是却不曾有富贵的人来家里。我打算暗中查看他是往哪儿去的。”翌日,她起了个大早,躲躲藏藏的跟踪丈夫走过的地方,走遍城中,竟没有一个人停下来跟他说话。最后走到东门外的坟地间,向上坟的人乞讨祭坟剩余的食物来吃,吃不够,又转而向另一处乞食。这就是他吃饱喝足的方法。他妻子回家,对他的妾说:“做丈夫的人是我们指望依靠他过一辈子的人,现在竟然如此!”两人埋怨责骂她们的丈夫,并且在庭中相向哭泣。然而这个做丈夫的还不知道,仍然喜滋滋的从外面回来,又向妻妾夸耀骄傲一番。
  用君子的眼光来看此事,那么世人追求富贵利达的奸邪行径,他们的妻妾不感觉羞耻,并且不相向哭泣的,恐怕是很少的。

析论:
   良人不良,自欺欺人,无耻之尤。孟子正是以冷隽的反讽,痛斥那些谄媚阿谀,巧取豪夺,以求富贵利达的无耻败德之徒。顾炎武说“博学于文”,“行己有耻”,这应该是每个人的自我要求。
   

存理克欲(二)

孟子曰:「魚,我所欲也;熊掌,亦我所欲也,二者不可得兼,舍魚而取熊掌者也。生,亦我所欲也;義,亦我所欲也,二者不可得兼,舍生而取義者也。生亦我所欲,所欲有甚於生者,故不為苟得也;死亦我所惡,所惡有甚於死者,故患有所不辟也。如使人之所欲莫甚於生,則凡可以得生者,何不用也?使人之所惡莫甚於死者,則凡可以辟患者,何不為也?由是則生而有不用也,由是則可以辟患而有不為也。是故所欲有甚於生者,所惡有甚於死者,非獨賢者有是心也,人皆有之,賢者能勿喪耳。」
「一簞食,一豆羹,得之則生,弗得則死。嘑爾而與之,行道之人弗受;蹴爾而與之,乞人不屑也。萬鍾則不辨禮義而受之。萬鍾於我何加焉?為宮室之美、妻妾之奉、所識窮乏者得我與?鄉為身死而不受,今為宮室之美為之;鄉為身死而不受,今為妻妾之奉為之;鄉為身死而不受,今為所識窮乏者得我而為之,是亦不可以已乎?此之謂失其本心。」

语译:
  孟子说:鱼是我喜欢的;熊掌也是我所喜欢的,假如这两者不能兼得,我情愿舍弃鱼而选择熊掌。生命是我所爱惜的;道义也是我所爱惜的,假如这两者只能取其一,我情愿舍弃生命而成全道义。生命是我所爱惜的,然而我所爱惜的有比生命还要贵重时,就不会苟且求生了;死亡时我所憎恶的,然而我所憎恶的有比死亡还要可厌,就不会苟且避开祸患了。假如一个人所爱惜的没有比生命更贵重了,那么凡是足以维持生命的手段,还有什么使不出来的呢?假如一个人所憎恶的没有比死亡更可厌的了,那么凡是足以避免死亡灾祸的事情,还有什么做不出来的呢?照这样做,就能生存下去,可是却有人不这样做,照这样做,就可以避免祸患,可是却有人不这样做。由此可知,人所爱惜的有比生命更珍贵的,人所憎恶的有比死亡更可厌。不只是贤德的人有这样崇尚道义的羞恶之心,每一个人都有啊!只是贤德的人能够存养本心而不丧失罢了。
  譬如有一小竹篮的饭,有一碗羹汤,饿极了的人得到了就能活命,得不到就要饿死。然而大声吆喝的给他吃,即使是一般的路人也不肯接受;假如是用脚拨给他,就连乞丐也不屑一顾了。然而一旦万钟厚禄当前,许多人就不去分辨是否合乎礼义而接受了。那万钟厚禄对我有什么助益呢?是为了住宅的华美,妻妾的奉侍,以及那些困穷的朋友可以得到我的好处而对我感激吗?从前情愿饿死也不愿意接受不礼貌的施舍,现在却为了华美的住宅就接受了;从前情愿饿死也不愿接受不礼貌的施舍,现在却为了妻妾的奉侍就接受了;从前情愿饿死也不愿意接受不礼貌的施舍,现在却为了要使那些困穷的朋友对自己感激就接受了,这种行为难道不可以停止吗?这种情形就叫做失去本有的良心。


析论:
   这篇文章在学校是背的烂熟,今天读来仍是回味无穷。人往往为私利所困,趋利避患,不择手段,无所不为。孟子点出“所欲有甚于生者”,“所恶有甚于死者”。须得秉承良知行事,方能自由。