嵌入式Linux实现关机断电

时间:2021-07-03 18:57:23

http://blog.chinaunix.net/uid-23028407-id-3085685.html

文章在描述时基于以下环境:

       硬件平台:Micro 2440;

       软件:Linux 2.6.32;

       撰写本文的目的在于知识分享,但因本人能力有限,难免有描述不当或错误的地方,欢迎大家批评指正。       

       在运行Linux系统的PC上,我们可以使用poweroff/halt等命令进行关机,但是在嵌入式Linux系统上想要通过运行这些命令,实现关机操作,就需要软硬件相互配合才能达成。

       首先,硬件在设计时需要有电源管理模块,能接收来自主芯片的信号并切断整个系统的电源,在具体的实现中,可以使用GPIO来给电源管理模块送出信号。为什么要用GPIO呢?有没有其它的方法?答案是:有!但是,使用GPIO最简单。如下图所示:

嵌入式Linux实现关机断电

       在需要关机时,在合适的时间点由主芯片通过GPIO送出信号给电源管理模块,电源管理模块在接收到关机信号后,将整个系统的电源切断即可,在具体的实现中,“电源管理模块”可以是纯粹的硬件电路,也可以是一个微控制器,如8051单片机等。

       那么怎么来确定这个合适的“时间点”呢?这个就是软件的任务了,在软件方面,需要修改poweroff & halt这两个系统调用的具体实现,说白了也很简单,在poweroff/halt系统调用执行完的时刻就是我们需要的“时间点”,所以我们要做的就是在系统调用的最后添加控制GPIO的代码。

       下面贴出代码简略的说明一下,poweroff/halt这两个系统调用最终会落在kernel2.6.x/kernel/sys.c文件的函数SYSCALL_DEFINE4(...)中,如下

  1.  360 SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
  2.  361 void __user *, arg)
  3.  362 {
  4.  363     char buffer[256];
  5.  364     int ret = 0;
  6.  365 
  7.  366     /* We only trust the superuser with rebooting the system. */
  8.  367     if (!capable(CAP_SYS_BOOT))
  9.  368         return -EPERM;
  10.  369 
  11.  370     /* For safety, we require "magic" arguments. */
  12.  371     if (magic1 != LINUX_REBOOT_MAGIC1 ||
  13.  372             (magic2 != LINUX_REBOOT_MAGIC2 &&
  14.  373                 magic2 != LINUX_REBOOT_MAGIC2A &&
  15.  374                 magic2 != LINUX_REBOOT_MAGIC2B &&
  16.  375                 magic2 != LINUX_REBOOT_MAGIC2C))
  17.  376         return -EINVAL;
  18.  377 
  19.  378     /* Instead of trying to make the power_off code look like
  20.  379     * halt when pm_power_off is not set do it the easy way.
  21.  380     */
  22.  381     if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
  23.  382         cmd = LINUX_REBOOT_CMD_HALT;
  24.  383 
  25.  384     lock_kernel();
  26.  385     switch (cmd) {
  27.  386     case LINUX_REBOOT_CMD_RESTART:
  28.  387         kernel_restart(NULL);
  29.  388         break;
  30.  389 
  31.  390     case LINUX_REBOOT_CMD_CAD_ON:
  32.  391         C_A_D = 1;
  33.  392         break;
  34.  393 
  35.  394     case LINUX_REBOOT_CMD_CAD_OFF:
  36.  395         C_A_D = 0;
  37.  396         break;
  38.  397 
  39.  398     case LINUX_REBOOT_CMD_HALT:
  40.  399         kernel_halt();
  41.  400         unlock_kernel();
  42.  401         do_exit(0);
  43.  402         panic("cannot halt");
  44.  403 
  45.  404     case LINUX_REBOOT_CMD_POWER_OFF:
  46.  405         kernel_power_off();
  47.  406         unlock_kernel();
  48.  407         do_exit(0);
  49.  408         break;
  50.  409 
  51.  410     case LINUX_REBOOT_CMD_RESTART2:
  52.  411         if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) {
  53.  412                 unlock_kernel();
  54.  413         return -EFAULT;
  55.  414     }
  56.  415     buffer[sizeof(buffer) - 1] = '\0';
  57.  416 
  58.  417     kernel_restart(buffer);
  59.  418     break;
  60.  419 
  61.  420 #ifdef CONFIG_KEXEC
  62.  421     case LINUX_REBOOT_CMD_KEXEC:
  63.  422         ret = kernel_kexec();
  64.  423         break;
  65.  424 #endif
  66.  425 
  67.  426 #ifdef CONFIG_HIBERNATION
  68.  427     case LINUX_REBOOT_CMD_SW_SUSPEND:
  69.  428         ret = hibernate();
  70.  429         break;
  71.  430 #endif
  72.  431 
  73.  432     default:
  74.  433     ret = -EINVAL;
  75.  434     break;
  76.  435     }
  77.  436     unlock_kernel();
  78.  437     return ret;
  79.  438 }

       在这个函数中有一个switch(cmd)语句,poweroff/halt系统调用最终会执行到 LINUX_REBOOT_CMD_HALT这个分支,如398-402行所示,在这个case分支中,明眼人很快就会发现kernel_halt()这个函数是核心。

       kernel_halt()的实现在同一个文件中:

  1.  321 /** 
  2.  322 * kernel_halt - halt the system
  3.  323 * 
  4.  324 * Shutdown everything and perform a clean system halt.
  5.  325 */ 
  6.  326 void kernel_halt(void)
  7.  327 { 
  8.  328     kernel_shutdown_prepare(SYSTEM_HALT);
  9.  329     sysdev_shutdown();
  10.  330     printk(KERN_EMERG "System halted.\n");
  11.  331     machine_halt();
  12.  332 }

       328行,为内核关闭做准备并关闭设备。

       329行,关闭系统设备。

       330行,系统已经挂起,此时打印“System halted.”通知用户。

       331行,machine_halt()machine级别的挂其,让我们看看其实现,在arch/arm/kernel/process.c中:

  1. 193 void machine_halt(void)
  2. 194 {
  3. 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();
}