内核earlyprintk选项

通过设置early_printk选项,可以在内核串口设备初始化之前,看到打印输出,对于调试内核早期的启动问题非常有帮助。如下可在grub命令行中,手动加载启动内核,增加内核参数:

grub> 
grub> linux \(hd0,msdos2)/vmlinuz earlyprintk=ttyS0,115200
grub> 
grub> boot
grub> 

对于x86平台,内核函数setup_early_printk处理earlyprintk选项,解析earlyprintk=之后的参数。主要是串口初始化early_serial_init,和注册函数early_console_register。

static int __init setup_early_printk(char *buf)
{
    int keep;

    if (early_console) return 0;

    keep = (strstr(buf, "keep") != NULL);

    while (*buf != '\0') {
        if (!strncmp(buf, "serial", 6)) {
            buf += 6;
            early_serial_init(buf);
            early_console_register(&early_serial_console, keep);
            if (!strncmp(buf, ",ttyS", 5))
                buf += 5;
        }
        if (!strncmp(buf, "ttyS", 4)) {
            early_serial_init(buf + 4);
            early_console_register(&early_serial_console, keep);
        }
        ...
		
early_param("earlyprintk", setup_early_printk);

如下串口初始化函数,可通过命令行earlyprintk指定串口的基地址如: earlyprintk=serial,0x3f8,115200,也可以指定串口编号由内核选择串口寄存器的基地址:earlyprintk=serial,ttyS0,115200。如果是后者,内核仅支持两个串口,即ttyS0和ttyS1,基地址分别为0x3f8和0x2f8。如果指定其它的ttySx,内核修改为ttyS0。

如果不指定波特率,内核默认使用9600。另外,earlyprintk也可以不指定serial选项。

#define DEFAULT_BAUD 9600

static __init void early_serial_init(char *s)
{
    unsigned divisor;
    unsigned long baud = DEFAULT_BAUD;
    char *e;

    if (*s == ',') ++s;

    if (*s) {
        unsigned port;
        if (!strncmp(s, "0x", 2)) {
            early_serial_base = simple_strtoul(s, &e, 16);
        } else {
            static const int __initconst bases[] = { 0x3f8, 0x2f8 };

            if (!strncmp(s, "ttyS", 4))
                s += 4;
            port = simple_strtoul(s, &e, 10);
            if (port > 1 || s == e)
                port = 0;
            early_serial_base = bases[port];
        }
        s += strcspn(s, ",");
        if (*s == ',')
            s++;
    }

    if (*s) {
        baud = simple_strtoull(s, &e, 0);

        if (baud == 0 || s == e)
            baud = DEFAULT_BAUD;
    }
	...
    /* Set up the HW */
    early_serial_hw_init(divisor);

以下初始化串口硬件寄存器,可见使用8个数据位,1个停止位,无奇偶校验位。

static __init void early_serial_hw_init(unsigned divisor)
{
    unsigned char c;

    serial_out(early_serial_base, LCR, 0x3);    /* 8n1 */
    serial_out(early_serial_base, IER, 0);  /* no interrupt */
    serial_out(early_serial_base, FCR, 0);  /* no fifo */
    serial_out(early_serial_base, MCR, 0x3);    /* DTR + RTS */

    c = serial_in(early_serial_base, LCR);
    serial_out(early_serial_base, LCR, c | DLAB);
    serial_out(early_serial_base, DLL, divisor & 0xff);
    serial_out(early_serial_base, DLH, (divisor >> 8) & 0xff);
    serial_out(early_serial_base, LCR, c & ~DLAB);

如果在earlyprintk中指定keep选项,如: earlyprintk=ttyS0,115200 keep,在注册终端时,如果有CON_BOOT标志,在系统串口驱动程序正常运行时,将自动注销此console口;否则,如果设置了keep选项,将清除CON_BOOT标记,保留此串口。

static void early_console_register(struct console *con, int keep_early)
{
    if (con->index != -1) {
        printk(KERN_CRIT "ERROR: earlyprintk= %s already used\n",
               con->name);
        return;
    }
    early_console = con;
    if (keep_early)
        early_console->flags &= ~CON_BOOT;
    else
        early_console->flags |= CON_BOOT;
    register_console(early_console);

在内核启动函数start_kernel中,earlyprintk由函数parse_early_param进行处理,所以,如果内核代码在之前发生panic等错误,earlyprintk也不能打印任何信息。

asmlinkage __visible void __init start_kernel(void)
{

    set_task_stack_end_magic(&init_task);
    smp_setup_processor_id();
    ...
    pr_notice("%s", linux_banner);
    early_security_init();
    setup_arch(&command_line);
    ...

    build_all_zonelists(NULL);
    page_alloc_init();

    pr_notice("Kernel command line: %s\n", boot_command_line);
    /* parameters may set static keys */
    jump_label_init();
    parse_early_param();

内核版本 5.10

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页