打开Linux内核启动早期的log
有时会遇到当在u-boot中执行完bootm后,打印出start kernel后串口就没有再输出任何信息了。此时就需要打开内核早期的log:
makemenuconfig
Kernel hacking --->
[*] Kernel low-level debugging functions(read help!)
Kernel low-level debugging port (Use Samsung S3C UART 0 for low-level debug)
[*] Early printk
对于earlyprintk,还需要在bootargs中添加参数earlyprintk才能生效,有了上面这几个配置,会有下面几个宏生效:
CONFIG_DEBUG_LL=y
CONFIG_DEBUG_S3C_UART0=y
CONFIG_DEBUG_LL_INCLUDE="debug/exynos.S"
CONFIG_DEBUG_UNCOMPRESS=y
CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h"
CONFIG_EARLY_PRINTK=y
关于earlyprintk的解析在文件arch/arm/kernel/early_printk.c中:
1: extern void printch(int);
2:
3: static voidearly_write(const char *s, unsigned n)
4: {
5: while (n-- >; 0) {
6: if (*s== '\n')
7: printch('\r');
8: printch(*s);
9: s++;
10: }
11: }
12:
13: static void early_console_write(struct console *con, const char *s, unsigned n)
14: {
15: early_write(s, n);
16: }
17:
18: static struct console early_console_dev = {
19: .name = "earlycon",
20: .write = early_console_write,
21: .flags = CON_PRINTBUFFER | CON_BOOT,
22: .index = -1,
23: };
24:
25: static int __initsetup_early_printk(char *buf)
26: {
27: early_console = &;early_console_dev;
28: register_console(&;early_console_dev);
29: return 0;
30: }
31:
32: early_param("earlyprintk", setup_early_printk);
其中printch都是通过汇编语言实现的。
在arch/arm/Kconfig.debug中可以看到:
configDEBUG_LL
bool "Kernel low-level debuggingfunctions (read help!)"
depends on DEBUG_KERNEL
help
Say Y here to include definitions of printascii,printch, printhex
in the kernel. This is helpful if you aredebugging code that
executes beforethe console is initialized.
configDEBUG_S3C_UART0
depends on PLAT_SAMSUNG
select DEBUG_EXYNOS_UART if ARCH_EXYNOS
select DEBUG_S3C24XX_UART if ARCH_S3C24XX
select DEBUG_S5PV210_UART if ARCH_S5PV210
bool "Use Samsung S3C UART 0 for low-level debug"
help
Say Y here if you want the debug printroutines to direct
their output to UART 0. The portmust have been initialised
by the boot-loaderbefore use.
configDEBUG_LL_INCLUDE
string
……
default "debug/exynos.S" ifDEBUG_EXYNOS_UART
configEARLY_PRINTK
bool "Early printk"
depends on DEBUG_LL
help
Say Y here if you want to have an earlyconsole using the
kernel low-level debuggingfunctions. Addearlyprintk to your
kernel parametersto enable this console.
从上面的信息我们可以知道:
· 在串口终端尚未注册时,内核定义了printascii、printch以及printhex用于调试;
· early console使用的也是上面定义的函数,需要在传递给内核的参数中添加earlyprintk参数
· Linux内核早期的print函数的输出串口要跟u-boot下使用的一致,即内核不再负责初始化了,让u-boot来做,所以二者一定要一致,否则那些print函数以及earlyprintk都没法输出信息;
· 可以参考arch/arm/kernel/debug.S,printascii、printch以及printhex都是在这里定义的;
· 在kernel进入C函数(start_kernel)后可以调用early_print来打印信息,它是在arch/arm/kernel/setup.c中定义的:
1: void __init early_print(const char *str, ...)
2: {
3: extern void printascii(const char *);
4: char buf[256];
5: va_list ap;
6:
7: va_start(ap, str);
8: vsnprintf(buf, sizeof(buf),str, ap);
9: va_end(ap);
10:
11: #ifdef CONFIG_DEBUG_LL
12: printascii(buf);
13: #endif
14: printk("%s", buf);
15: }
可以看到,early_print也会调用printascii和printk,意思是用early_print打印的信息可能会重复出现在终端上(printk会缓冲一部分,当bootconsole注册后,会将printk缓冲区中的内容输出)。
上面所说的打印函数只能在内核自解压后的函数中才能使用,那么内核自解压过程中的信息是不是也可以打印呢?可以,内核自解压相关的文件在arch/arm/boot/compressed/下面,我们所熟知的:
Uncompressing Linux... done, booting the kernel.
就是这个目录下的代码打印出来的,具体代码如下:
arch/arm/boot/compressed/misc.c
1: void
2: decompress_kernel(unsigned long output_start, unsigned longfree_mem_ptr_p,
3: unsigned longfree_mem_ptr_end_p,
4: int arch_id)
5: {
6: ......
7: putstr("Uncompressing Linux...");
8: ret = do_decompress(input_data,input_data_end - input_data,
9: output_data, error);
10: ......
11: putstr(" done, booting the kernel.\n");
12: }
其中,putstr的定义如下:
1: static void putstr(const char *ptr)
2: {
3: char c;
4:
5: while ((c = *ptr++) != '\0') {
6: if (c =='\n')
7: putc('\r');
8: putc(c);
9: }
10:
11: flush();
12: }
putc是汇编实现的,arch/arm/boot/compressed/debug.S:
1: #include CONFIG_DEBUG_LL_INCLUDE
2:
3: ENTRY(putc)
4: addruart r1, r2, r3
5: waituart r3, r1
6: senduart r0, r1
7: busyuart r3, r1
8: mov pc, lr
9: ENDPROC(putc)
10:
11:
其中addruart的实现因soc的不同而不同,对于exynos4412,它的实现是(arch/arm/include/debug/exynos.S):
1: .macro addruart, rp, rv, tmp
2: mrc p15, 0, \tmp, c0, c0, 0
3: and \tmp, \tmp, #0xf0
4: teq \tmp, #0xf0 @@ A15
5: ldreq \rp, =EXYNOS5_PA_UART
6: movne \rp, #EXYNOS4_PA_UART @@ EXYNOS4
7: ldr \rv, =S3C_VA_UART
8: CONFIG_DEBUG_S3C_UART != 0
9: add \rp, \rp, #(0x10000 * CONFIG_DEBUG_S3C_UART)
10: add \rv, \rv, #(0x10000 * CONFIG_DEBUG_S3C_UART)
11: if
12: .endm
这个函数的目的就是获得控制调试uart的寄存器的物理基地址(rp)和虚拟基地址(rv),这里也没有初始化uart的代码,所以必须跟u-boot使用的串口一致。