全志A33-linux内核early_printk分析及使用
分析Linux内核启动流程时可以知道,在调用setup_arch函数之前就已经调用过printk函数了,但是这个时候的printk函数只是将打印信息放在缓存区中,并没有打印到控制台上,因为这个时候控制台还没有被初始化。
只有在start_kernel函数中的console_init函数被调用后,控制台才会被注册、初始化,printk函数打印的内容才会被真正地输出到屏幕上。如果想在console_init函数之前打印信息,需要调用early_printk函数。
1. 内核配置
(1)Kernel hacking ---> Kernel low-level debugging functions --> Early printk
(2)boot option中需要添加 earlyprintk项。
类似于:earlyprintk=ttyS0,115200 loglevel=9 initcall_debug=1 console=ttyS0,115200 ramfs
2. 使用
在需要打印调试信息的地方调用此函数
early_printk("------------xxx------------\n");
3. A33开发板
如在./arch/arm/mach-sunxi/sun8i.c 中有如下语句:
early_printk("[%s]: From boot, get meminfo:\n", __func__);
启动开发板时串口控制台信息如下:
在没有启用early_printk之前,串口控制台信息如下:
4. 对early printk的驱动实现的分析
arch/arm/kernel/early_printk.c文件,上代码:
- extern void printch(int);
- static void early_write(const char *s, unsigned n)
- {
- while (n-- > 0) {
- if (*s == '\n')
- printch('\r');
- printch(*s);
- s++;
- }
- }
- static void early_console_write(struct console *con, const char *s, unsigned n)
- {
- early_write(s, n);
- }
- static struct console early_console = {
- .name = "earlycon",
- .write = early_console_write,
- .flags = CON_PRINTBUFFER | CON_BOOT,
- .index = -1,
- };
- asmlinkage void early_printk(const char *fmt, ...)
- {
- char buf[512];
- int n;
- va_list ap;
- va_start(ap, fmt);
- n = vscnprintf(buf, sizeof(buf), fmt, ap);
- early_write(buf, n);
- va_end(ap);
- }
- static int __init setup_early_printk(char *buf)
- {
- register_console(&early_console);
- return 0;
- }
- early_param("earlyprintk", setup_early_printk);
其实这段code最终的实现都是靠:extern void printch(int);这个函数。这个函数实现是在:arch/arm/kernel/debug.S中:
- ENTRY(printch)
- addruart_current r3, r1, r2
- mov r1, r0
- mov r0, #0
- b 1b
- ENDPROC(printch)
- .macro addruart_current, rx, tmp1, tmp2
- addruart \tmp1, \tmp2
- mrc p15, 0, \rx, c1, c0
- tst \rx, #1
- moveq \rx, \tmp1
- movne \rx, \tmp2
- .endm
printch会调用到 addruart_current函数,而addruart_current函数用调用到:addruart函数,该函数实现是在:arch\arm\mach-s3c64xx\include\mach中的debug-macro.S汇编文件中:
- .macro addruart, rp, rv
- ldr \rp, = S3C_PA_UART
- ldr \rv, = (S3C_VA_UART + S3C_PA_UART & 0xfffff)
- #if CONFIG_DEBUG_S3C_UART != 0
- add \rp, \rp, #(0x400 * CONFIG_DEBUG_S3C_UART)
- add \rv, \rv, #(0x400 * CONFIG_DEBUG_S3C_UART)
- #endif
- .endm
我们从上面的代码可以看到S3C_PA_UART, S3C_PA_UART都是实际的6410的串口寄存器物理和虚拟地址,从而进行真正的硬件底层操作。