基于A64的linux4.18内核进入panic后无法自动重启
背景
最近几天公司的一个做矿机的客户遇到了这样一种情况:使用tf卡加载程序时,内核进入panic可以自动重启;使用nor flash时,内核进入panic无法自动重启。由于客户的挖矿管理程序bug很多,很容易造成不同情况的panic,但是量产在即,容不得那么多时间去一一排查bug,所以就往内核添加了只要进入panic就自动重启的配置,来规避这些bug。客户找到我,让我帮忙解决内核进入panic后无法自动重启的bug,毕竟我们是提供芯片的,量还比较大,所以就有了后面的文档。
平台
- 芯片:全志A64
- 内核:linux4.18
- flash烧写方法:采用dd命令
复现bug现象
拿到内核源码进行编译
拿到客户的源码后,在编译环境顶层目录下,第一次运行编译指令:
make
上述操作是生成整个内核源码和tf卡映像文件scard.img,scard.img是后面用来升级nor flash中代码使用的。之后每次修改内核代码时,直接使用如下指令:
make linux-rebuild
上述操作是编译内核源码的命令,使其生成映像文件Image.lzma。之后这映像文件要被写入到nor flash上。
烧录代码
客户这个a64的板子是先通过tf卡加载内核,进入文件系统的。然后再插根网线,使用ssh连接进入到目标机的内核文件系统。
进入内核文件系统之前,先通过ssh将Image.lzma拷贝到目标机的tmp目录下:
scp Image.lzma root@192.168.2.21:/tmp
拷贝完成之后,进入到目标机的文件系统中,执行nor flash写入命令:
dd if=/tmp/Image.lzma of=/dev/mtdblock3 bs=4k
等待dd写入完成后,拔掉tf卡,断电重启,就会从nor flash执行新的内核程序。
复现步骤
使用如下指令,主动触发panic:
echo 1 > /proc/sys/kernel/sysrq
echo c > /proc/sysrq-trigger
执行上述步骤后,会进入panic,log如下:
# echo 1 > /proc/sys/kernel/sysrq
# echo c > /proc/sysrq-trigger
[ 116.820488] sysrq: SysRq : Trigger a crash
[ 116.824659] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
[ 116.833448] Mem abort info:
[ 116.836291] ESR = 0x96000046
[ 116.839360] Exception class = DABT (current EL), IL = 32 bits
[ 116.845296] SET = 0, FnV = 0
[ 116.848361] EA = 0, S1PTW = 0
[ 116.851519] Data abort info:
[ 116.854410] ISV = 0, ISS = 0x00000046
[ 116.858263] CM = 0, WnR = 1
[ 116.861237] user pgtable: 4k pages, 48-bit VAs, pgdp = 0000000041ff400f
[ 116.867866] [0000000000000000] pgd=00000000471b5003, pud=00000000497d1003, pmd=0000000000000000
[ 116.876575] Internal error: Oops: 96000046 [#1] PREEMPT SMP
[ 116.882140] Modules linked in: at24 lm75 gpio_counter(O) soft_pwm(O) 8021q garp stp mrp llc
[ 116.890514] CPU: 0 PID: 1115 Comm: sh Tainted: G O 4.18.0 #19
[ 116.897550] Hardware name: Clover10 (DT)
[ 116.901470] pstate: 60000005 (nZCv daif -PAN -UAO)
[ 116.906266] pc : sysrq_handle_crash+0x20/0x2c
[ 116.910618] lr : sysrq_handle_crash+0xc/0x2c
[ 116.914880] sp : ffff00000abdbcf0
[ 116.918189] x29: ffff00000abdbcf0 x28: ffff80000cb21a00
[ 116.923498] x27: ffff0000085e1000 x26: 0000000000000040
[ 116.928805] x25: 0000000000000125 x24: 0000000000000000
[ 116.934113] x23: 0000000000000063 x22: 0000000000000007
[ 116.939421] x21: ffff0000090dd000 x20: ffff0000090c8000
[ 116.944728] x19: ffff000009117c80 x18: 000000000000000a
[ 116.950037] x17: 0000000000000000 x16: 0000000000000000
[ 116.955345] x15: 00000000000c8508 x14: ffff000089158ce7
[ 116.960653] x13: ffffffffffffffff x12: 0000000000000030
[ 116.965961] x11: 00000000fffffffe x10: ffff000009158cef
[ 116.971269] x9 : 0000000005f5e0ff x8 : 0000000000000000
[ 116.976577] x7 : 54203a2071527379 x6 : 0000000000000007
[ 116.981885] x5 : 0000000000000000 x4 : 0000000000000000
[ 116.987193] x3 : ffffffffffffffff x2 : b2c272ddd92d4900
[ 116.992500] x1 : 0000000000000000 x0 : 0000000000000001
[ 116.997810] Process sh (pid: 1115, stack limit = 0x000000008542075c)
[ 117.004152] Call trace:
[ 117.006598] sysrq_handle_crash+0x20/0x2c
[ 117.010605] __handle_sysrq+0x9c/0x14c
[ 117.014351] write_sysrq_trigger+0x74/0x7c
[ 117.018445] proc_reg_write+0x68/0x98
[ 117.022108] __vfs_write+0x34/0x140
[ 117.025594] vfs_write+0xb4/0x188
[ 117.028906] ksys_write+0x5c/0xbc
[ 117.032220] sys_write+0xc/0x14
[ 117.035360] el0_svc_naked+0x30/0x34
[ 117.038936] Code: 52800020 b90e2820 d5033e9f d2800001 (39000020)
[ 117.045022] ---[ end trace f45817332c9b5658 ]---
[ 117.049634] Kernel panic - not syncing: Fatal exception
[ 117.054855] SMP: stopping secondary CPUs
[ 117.058778] Kernel Offset: disabled
[ 117.062263] CPU features: 0x24802004
[ 117.065833] Memory Limit: none
[ 117.068890] Rebooting in 1 seconds..
上面是整个触发过程的log,现在是会一直停在Rebooting in 1 seconds上,没有其它反应,这个使用的是nor flash。
如果上述过程使用的是tf加载的话,再进行上述手动进入panic操作,就可以正常重启。
源码跟踪
加入打印后跟踪到代码一直停在emergency_restart()这个函数内部:
目前不清楚为啥一直停在这个函数内部,看样子是当时的环境不允许执行重启操作。因为这个函数并不是一个完整的重启,可能在重启前需要使用到一些环境条件。所以才可能出现一直停在函数内部的情况。
解决方案
从emergency_restart函数的注释可以看出,这是一个不完全的重启操作,在重启前应该需要一些环境条件满足,才会重启。所以将其替换为kernel_restart函数,通过注释可以发现,这个函数直接完全地重启:
/**
* kernel_restart - reboot the system
* @cmd: pointer to buffer containing command to execute for restart
* or %NULL
*
* Shutdown everything and perform a clean reboot.
* This is not safe to call in interrupt context.
*/
void kernel_restart(char *cmd)
{
kernel_restart_prepare(cmd);
migrate_to_reboot_cpu();
syscore_shutdown();
if (!cmd)
pr_emerg("Restarting system\n");
else
pr_emerg("Restarting system with command '%s'\n", cmd);
kmsg_dump(KMSG_DUMP_RESTART);
machine_restart(cmd);
}
EXPORT_SYMBOL_GPL(kernel_restart);
所以解决的方案如下:
if (panic_timeout != 0) {
kernel_restart(0);
/*
* This will not be a clean reboot, with everything
* shutting down. But if there is a chance of
* rebooting the system it will be rebooted.
*/
// emergency_restart();
}
将emergency_restart屏蔽掉,替换为kernel_restart即可。kernel_restart被调用时,需要smp调度关闭,中断关闭等。