http://blog.chinaunix.net/uid-23028407-id-3085685.html
文章在描述时基于以下环境:
硬件平台:Micro 2440;
软件:Linux 2.6.32;
撰写本文的目的在于知识分享,但因本人能力有限,难免有描述不当或错误的地方,欢迎大家批评指正。
在运行Linux系统的PC上,我们可以使用poweroff/halt等命令进行关机,但是在嵌入式Linux系统上想要通过运行这些命令,实现关机操作,就需要软硬件相互配合才能达成。
首先,硬件在设计时需要有电源管理模块,能接收来自主芯片的信号并切断整个系统的电源,在具体的实现中,可以使用GPIO来给电源管理模块送出信号。为什么要用GPIO呢?有没有其它的方法?答案是:有!但是,使用GPIO最简单。如下图所示:
在需要关机时,在合适的时间点由主芯片通过GPIO送出信号给电源管理模块,电源管理模块在接收到关机信号后,将整个系统的电源切断即可,在具体的实现中,“电源管理模块”可以是纯粹的硬件电路,也可以是一个微控制器,如8051单片机等。
那么怎么来确定这个合适的“时间点”呢?这个就是软件的任务了,在软件方面,需要修改poweroff & halt这两个系统调用的具体实现,说白了也很简单,在poweroff/halt系统调用执行完的时刻就是我们需要的“时间点”,所以我们要做的就是在系统调用的最后添加控制GPIO的代码。
下面贴出代码简略的说明一下,poweroff/halt这两个系统调用最终会落在kernel2.6.x/kernel/sys.c文件的函数SYSCALL_DEFINE4(...)中,如下:
- 360 SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
- 361 void __user *, arg)
- 362 {
- 363 char buffer[256];
- 364 int ret = 0;
- 365
- 366 /* We only trust the superuser with rebooting the system. */
- 367 if (!capable(CAP_SYS_BOOT))
- 368 return -EPERM;
- 369
- 370 /* For safety, we require "magic" arguments. */
- 371 if (magic1 != LINUX_REBOOT_MAGIC1 ||
- 372 (magic2 != LINUX_REBOOT_MAGIC2 &&
- 373 magic2 != LINUX_REBOOT_MAGIC2A &&
- 374 magic2 != LINUX_REBOOT_MAGIC2B &&
- 375 magic2 != LINUX_REBOOT_MAGIC2C))
- 376 return -EINVAL;
- 377
- 378 /* Instead of trying to make the power_off code look like
- 379 * halt when pm_power_off is not set do it the easy way.
- 380 */
- 381 if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
- 382 cmd = LINUX_REBOOT_CMD_HALT;
- 383
- 384 lock_kernel();
- 385 switch (cmd) {
- 386 case LINUX_REBOOT_CMD_RESTART:
- 387 kernel_restart(NULL);
- 388 break;
- 389
- 390 case LINUX_REBOOT_CMD_CAD_ON:
- 391 C_A_D = 1;
- 392 break;
- 393
- 394 case LINUX_REBOOT_CMD_CAD_OFF:
- 395 C_A_D = 0;
- 396 break;
- 397
- 398 case LINUX_REBOOT_CMD_HALT:
- 399 kernel_halt();
- 400 unlock_kernel();
- 401 do_exit(0);
-
402 panic("cannot halt");
- 403
- 404 case LINUX_REBOOT_CMD_POWER_OFF:
- 405 kernel_power_off();
- 406 unlock_kernel();
- 407 do_exit(0);
- 408 break;
- 409
- 410 case LINUX_REBOOT_CMD_RESTART2:
- 411 if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) {
- 412 unlock_kernel();
- 413 return -EFAULT;
- 414 }
- 415 buffer[sizeof(buffer) - 1] = '\0';
- 416
- 417 kernel_restart(buffer);
- 418 break;
- 419
- 420 #ifdef CONFIG_KEXEC
- 421 case LINUX_REBOOT_CMD_KEXEC:
- 422 ret = kernel_kexec();
- 423 break;
- 424 #endif
- 425
- 426 #ifdef CONFIG_HIBERNATION
- 427 case LINUX_REBOOT_CMD_SW_SUSPEND:
- 428 ret = hibernate();
- 429 break;
- 430 #endif
- 431
- 432 default:
- 433 ret = -EINVAL;
- 434 break;
- 435 }
- 436 unlock_kernel();
- 437 return ret;
- 438 }
在这个函数中有一个switch(cmd)语句,poweroff/halt系统调用最终会执行到 LINUX_REBOOT_CMD_HALT这个分支,如398-402行所示,在这个case分支中,明眼人很快就会发现kernel_halt()这个函数是核心。
kernel_halt()的实现在同一个文件中:
- 321 /**
- 322 * kernel_halt - halt the system
- 323 *
- 324 * Shutdown everything and perform a clean system halt.
- 325 */
- 326 void kernel_halt(void)
- 327 {
- 328 kernel_shutdown_prepare(SYSTEM_HALT);
- 329 sysdev_shutdown();
- 330 printk(KERN_EMERG "System halted.\n");
- 331 machine_halt();
- 332 }
328行,为内核关闭做准备并关闭设备。
329行,关闭系统设备。
330行,系统已经挂起,此时打印“System halted.”通知用户。
331行,machine_halt(),machine级别的挂其,让我们看看其实现,在arch/arm/kernel/process.c中:
- 193 void machine_halt(void)
- 194 {
- 195 }
看到了吧,这个函数并没有真正的实现,而我们前面说到的“时间点”,就是代码执行到这里的时刻,所以,我们只要在这个函数中控制GPIO即可配合硬件实现关机操作。
#include <linux/gpio.h>
void kernel_halt(void)
{
kernel_shutdown_prepare(SYSTEM_HALT);
sysdev_shutdown();
printk(KERN_EMERG "System halted.\n");
gpio_direction_output(57, 0);
machine_halt();
}