文章在描述时基于以下环境:
硬件平台: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即可配合硬件实现关机操作。