Acpi 和 apm 主要是针对在X86架构上存在bios芯片上的linux电源管理机制。
acpi是新更多的依赖于操作系统,是今后发展的主流。同时还增加了cpu 和battery相关的状态检测。
在嵌入式系统上,由于不存在bios的硬件则在内核编译时就没有对应的配置选项。只存在apm emulation的模拟。此部分只支持几种apm的event,但在应用层可以使用apmd来进行相应处理。
Linux的关机流程分为两种方式:
1.acpi 或者apm。如果在嵌入式系统上没有对应部分的支持则应该使用第二种方法。
2.sys_reboot。系统调用,在应用层存在对应的库函数,reboot()。
在内核中,对应参数会转到kernel_power_off,kernel_halt,kernel_restart
对应转到
machine_power_off,machine_halt,machine_restart
对应转到
pm_power_off ,....
pm_power_off 会准对不同平台的关机方式,进行处理,使电源关闭
2009-09-18
这几天跟刘兴宝共同调查了,嵌入式电源管理方面的问题。
关机的流程已经在上面的叙述中提及。
今天又在marvell的电路板上模拟运行了所构想的方案。使用apm emulation + ampd + input_handle ,在input_handle中使用按键F1,F2,F3,F4,模拟电源事件。
昨天晚上在春晖的机器上,在strachbox 下,交叉编译了apmd。没有棘手的问题。整个编译只依赖于 apmd.c,apm.h,apmlib.c。
昨天晚上没有试通这个想法,因为marvell电路板上的linux 内核已经编译进了apm emulation。而这个情况我不知道,所以又重新以模块的方式编译了apm emulation。当我将编好的ko插入marvell的内核时,系统提示内核已经存在了apm emulation。于是查看了apm emulation 的应用层表现(/dev/apm-bios--event,/proc/apm--data),发现确实已经在内核编译过程中选择了将apm emulation 编了内核。
所以昨晚的一段时间是浪费了。
apmd昨天下午就交叉编译好了,拷到marvell中,log信息提示正常。但是没有内核层的事件驱动,apmd自身无法进行其他更有意义的操作。
今天早上。编辑了input驱动apm event 的底层驱动程序。该程序大部分是从imx31内核源码的apm-power.c中,拷贝的。因为该部分的代码实现的正好是我所需要的input_handle,关于键盘处理的代码。
将全篇拷贝过来,修改其中的,input event mask为 EV_KEY,这样就可以接受所有的key事件,这样如果keycode是F1,F2,F3,F4,就可以向apm event queue 中发送事件,以触发apmd 读取对应的事件。apmd在收到事件时,会通过/proc/apm 读取对应事件的详细信息。此部分通过内核中的 apm_get_power_status 函数获得,所以要重写该函数指针。使用get_power_status模拟对应的power status,即填充info结构。这样驱动就ok了。
驱动是编辑好了,可以需要一个交叉编译环境和对应的内核源码header。以前在编译驱动时,都是存在一个header,这样就可以在make是用C选项指定header的位置。但是,现在没有header,上网查找一下,得到了方法。将 apm_support.c拷贝到 driver/char 下。修改该目录下的Makefile。在其中添加 objs-m += apm_support.o。然后重新make 是就可以看到生成了apm_support.ko。
同样用U盘将apm_support.ko拷贝到marvell /home 下。Insmod apm_support.ko。成功!
运行apmd。F1,F2,F3,F4。Great!apmd的log上清晰的提示着,不同的apm event。
下一步就是测试apmd的callproxy 是否好用了。该proxy对应的是/etc/apmd_proxy。于是在/etc下vi 一个 apmd_proxy ,并chmod为可执行属性。在apmd_proxy中,可以用touch,或者mkdir做简单的测试。
好了,成功了。按下不同的按键,就会在 /home 下创建对应的文件。这预示着从底层到应用层的所有路径都已经打通了。剩下要做的就是,在移植到实际硬件中时,修改apm event 的发起者和apm_get_power_status 的内部数据。而在用户层,要做的就是修改apmd_proxy中的电源管理策略(比如,按顺序关闭某些进程,关闭背光等等)。
总结。虽然最后的实现过程仅仅只有一天不到的时间,但前期的调查却使用了大约一个星期的时间。这个期间,每天都在内核中寻找关于apm的蛛丝马迹,甚至同时参阅3个版本的内核源码。从最初的对acpi和apm的初略了解,到最后的使用apm emulation模拟实际apm,整个过程,每天都是在不断更新对linux 电源管理的认识。其中进步最大的,提速最快的是与刘兴宝的几次交流,每个人都不可能了解到所有方面,而或许对某个方面的一个小小问题都能带来新的思考,从而打开这个方面更全面更新的认识。
由于跟随项目的需求,查看了电源管理芯片mc13783的驱动。觉得其中的电源事件可以用来驱动apm 的 event。这样就省略了大部分的编码,复用了极大部分的内核代码。在mc13783中断事件的中断服务函数下半部中可以 向apm 发送event,同时准备好power_status 。在硬件完整的情况下,该部分应该在2天以内完成。
对于应用层的电源管理策略,我不再进行跟踪了,这是一个简单的过程。在suspend时,按照一定的顺序关掉一组进程和服务,在resume时,按照相反的顺序启动进程和服务。在low battery时,关闭某些功耗设备和进行。在change power 时,实时显示电源状态,等等。
至于关闭进程的顺序,可以参照pc 机上 acpi 的关机脚本的顺序来进行。Ubuntu下,该目录位于 /etc/acpi/suspend.d ,/etc/acpi/resume.d,/etc/acpi/battery.d 。其中(标号S代表start,K代表kill)数字标号越小,代表越先执行。
2008-09-18 下午
注:根据需求可能需要修改的地方。在apm emulation中,默认的suspend是PM_SUSPEND_MEM,如果需要改变关机状态到DSM,那么就需要修改这个suspend 的参数。----在此提及,以防出现恐慌。
个人想法:
在项目的需求中,由于用到了“关机”状态led指示,所以只是假关机,实际只是进入了DSM(deep sleep mode)。每次电源事件都会唤醒CPU,CPU处理后,继续进入DSM。
而进入DSM的操作由apm来实现,所以系统命令halt,shutdown,poweroff,都处于无效状态。原因是,没有在内核中实现对应的操作代码。可以使用这些系统命令来进行完全关机(掉电),而不是使CPU进入DSM状态。要实现这个功能,只需简单的将power_off指针转到mc13783的poweroff函数就可以了。
这样最终系统的可以操作的结果就是:
1.按下电源键,系统通过apm进入DSM的假关机状态,此时有充电指示。
2.在终端下,输入halt。系统立即完全掉电。
2009-09-18
关于电源状态
以上主要描述的是系统的关机流程。而完整的电源管理还包含电源状态的检测甚至调整。而acpi还包括了对CPU,Fan,等等其他系统硬件的管理。
下面讲述一下嵌入式(arm)下的电源状态管理。
1.apm。
同样通过apm emulation来进行管理。在power staus 中涵盖了AC,battery的状态。因此可以通过apmd在应用层读取到对应的电源状态。如果应用层没有syslogd,那么最后的信息会打印到控制台。否则,信息进入/var/log/apm中。
2./sys/class/power_supply
在该文件夹下,可以支持四种不同的供电方式:battery,main,UPS,USB。其中可选的属性有很多:POWER_SUPPLY_PROP_STATUS = 0,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_VOLTAGE_MIN,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_EMPTY,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_AVG,
POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN,
POWER_SUPPLY_PROP_ENERGY_FULL,
POWER_SUPPLY_PROP_ENERGY_EMPTY,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_ENERGY_AVG,
POWER_SUPPLY_PROP_CAPACITY, /* in percents! */
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_AMBIENT,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
/* Properties of type `const char *' */
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_SERIAL_NUMBER,
这样针对不同类型的电源,就有详细的属性可以来观察检测。
使用这种方法的过程是:将电源模拟成device,作为device的驱动插入内核,在device的注册过程中的probe中,使用power_supply_register进行注册。其中最要的操作get_property
需要device来提供。最后,power_supply注册到power_supply_class,power_supply_class被作为EXPORT_SYMBOL_GPL,那么其实在apm中也可以关注到该device的property(用于apm_get_power_status的实现)。
查看了pda关于电源管理的部分。
通常在内核中起一个定时器,或者工作队列,或者线程,不断检测电源的property,如果发生改变,就使用power_supply_changed 函数通知电源状态发生了改变。这个函数最终调用psy的 external_power_changed 函数。同时他会发出一个uevent(CHANGE),通知应用层。所以在应用层可以通过external_power_changed 中的机制获得信息,亦可以通过udev来简单的获得uevent,进而做出处理。
总结:比较两种电源检测的方法,实际上apm是power_supply的上一层次。apm可以通过power_supply_class获知电源状态信息,进而通过apmd进行管理策略上的调整。power_supply 是一种比较全面的对于电源设备的检测的方法,既可以为apm提供底层的数据支持,也可以进行其他扩展,通过uevent与用户层通信。一个完整的系统应该需要两个部分的共同支持:apm的suspend,和策略调整,power_supply的对电源的详细属性的提供。
如果需要获得电源的详细属性,则需要明确提供power_supply的支持。