[console] earlycon实现流程

本文以samsung s5pv210(ARM)为例

一、功能说明&使用方法
1、功能说明
printk的log输出是由console实现(会在其他文章中说明)。由于在kernel刚启动的过程中,还没有为串口等设备等注册console(在device probe阶段实现),此时无法通过正常的console来输出log。
为此,linux提供了early console机制,用于实现为设备注册console之前的早期log的输出,对应console也称为boot console,简称bcon。这个console在kernel启动的早期阶段就会被注册,主要通过输出设备(比如串口设备)的简单的write方法直接进行数据打印。而这个write方法也就是平台实现。
注意,这时候作为输出的串口设备是基于bootloader中已经初始化完成的。
early console机制有两种实现方式,早期的early_printk实现和后面的earlycon实现。在这里主要说明early_con的实现方式

2、earlycon和early_printk的差异
earlycon比early_printk更新的一种early console机制。
在early_printk中,平台主要实现early_console->write的最终流程addruart函数,各个平台通过函数定义的方式来进行实现,兼容性较差。
并且uart寄存器地址都是由自己定义,和正常console的定义完全独立开来,可移植性较差。
而在earlycon中,其通过__earlycon_table维护所有的earlycon_id,通过dts中的正常console的compatible获取到所需要使用的earlycon_id,兼容性较好。
并且dts获取正常console使用的uart寄存器地址来作为earlycon write实现中的uart寄存器基址,可移植性较好。
关于early_printk请参考《[console] early_printk实现流程》一文

3、earlycon使用方法简介
(1)打开对应宏

CONFIG_SERIAL_EARLYCON
CONFIG_OF_EARLY_FLATTREE


对应平台需要打开对应earlycon_id实现的宏,以tiny210(s5pv210平台)为例

CONFIG_SERIAL_SAMSUNG_CONSOLE

(2)在cmdline中添加earlycon。
以tiny210为例,使用的是bootargs来传递cmdline,所以在bootargs中添加“earlycon”

    chosen {
        bootargs = "console=ttySAC0,115200n8 root=/dev/ram rw rootwait ignore_loglevel earlycon";
        linux,stdout-path = &uart0;
    };  

(3)在dts中为chosen节点添加“linux,stdout-path”或者“stdout-path”属性,这个属性指定要用作标准输入输出的dts节点路径。
以tiny210为例

    chosen {
        bootargs = "console=ttySAC0,115200n8 root=/dev/ram rw rootwait ignore_loglevel earlycon";
        linux,stdout-path = &uart0;
    };  

(4)调用printk进行打印。

二、earlycon定义
(1)每个earlycon都对应一个earlycon_id,所有的earlycon_id都被维护__earlycon_table中
定义方法如下:

OF_EARLYCON_DECLARE(s5pv210, "samsung,s5pv210-uart",
            s5pv210_early_console_setup);

OF_EARLYCON_DECLARE展开如下:
include/linux/serial_core.h

#define OF_EARLYCON_DECLARE(_name, compat, fn)                \
    static const struct earlycon_id __UNIQUE_ID(__earlycon_##_name)    \
         __used __section(__earlycon_table)                \
        = { .name = __stringify(_name),                \
            .compatible = compat,                \
            .setup = fn  }

s5pv210对应如下:

static const struct earlycon_id __UNIQUE_ID(__earlycon_s5pv210)    \
         __used __section(__earlycon_table)                \
        = { .name = s5pv210,                \
            .compatible = "samsung,s5pv210-uart",                \
            .setup = s5pv210_early_console_setup}

这些earlycon_id都放在__earlycon_table中。

(2)earlycon_id定义如下:
include/linux/serial_core.h

struct earlycon_id {
        char    name[16];
        char    compatible[128];
        int    (*setup)(struct earlycon_device *, const char *options);
} __aligned(32);

char name:earlycon的名字,最终会作为相应console的名称。
char compatible:用于匹配uart对应的dts node。
setup:用来为earlycon设置write函数

三、earlycon安装
0、结构体说明
(1)earlycon_device
include/linux/serial_core.h

struct earlycon_device {
    struct console *con;
    struct uart_port port;
    char options[16];        /* e.g., 115200n8 */
    unsigned int baud;
};

struct console *con:用来往console子系统中注册的console。
struct uart_port port:对应的串口的uart_port,需要在解析earlycon的过程中设置
char options[16]:earlycon的参数,选项
unsigned int baud:波特率

1、首先解析cmdline中的earlycon参数
drivers/of/fdt.c

early_param("earlycon", setup_of_earlycon);
static int __init setup_of_earlycon(char *buf)
{
    if (buf)
        return 0;
    return early_init_dt_scan_chosen_serial();
}

调用early_init_dt_scan_chosen_serial来搜索dts,找到相应的节点并进一步安装。

2、early_init_dt_scan_chosen_serial实现
drivers/of/fdt.c

#ifdef CONFIG_SERIAL_EARLYCON
static int __init early_init_dt_scan_chosen_serial(void)
{
       offset = fdt_path_offset(fdt, "/chosen");
    if (offset < 0)
        offset = fdt_path_offset(fdt, "/chosen@0");
    if (offset < 0)
        return -ENOENT;

    p = fdt_getprop(fdt, offset, "stdout-path", &l);
    if (!p)
        p = fdt_getprop(fdt, offset, "linux,stdout-path", &l);
    if (!p || !l)
        return -ENOENT;

    q = strchrnul(p, ':');
    if (*q != '\0')
        options = q + 1;
    l = q - p;

    /* Get the node specified by stdout-path */
    offset = fdt_path_offset_namelen(fdt, p, l);
    if (offset < 0) {
        pr_warn("earlycon: stdout-path %.*s not found\n", l, p);
        return 0;
    }    
       for (match = __earlycon_table; match < __earlycon_table_end; match++) {
           if (!match->compatible[0])
               continue;
            if (fdt_node_check_compatible(fdt, offset, match->compatible))
               continue;
           of_setup_earlycon(match, offset, options);
           return 0;
       }
       return -ENODEV;
}



首先从chosen节点中获取stdout-path或者linux,stdout-path属性,这两个属性指明了标准输入输出串口的dtsi节点路径,以tiny210为例:

linux,stdout-path = &uart0;
1
通过匹配compatible来查找标准输入输出串口的dtsi对应的earlycon_id。
例如:tiny210的标准输入输出串口节点如下(使用的是uart0)

        uart0: serial@e2900000 {
            compatible = "samsung,s5pv210-uart";
        ...

s5pv210的earlycon_id的compatible也是”samsung,s5pv210-uart”,所以会匹配到s5pv210的earlycon_id,然后调用of_setup_earlycon进行下一步setup。

3、of_setup_earlycon实现如下
drivers/tty/serial/earlycon.c

#ifdef CONFIG_OF_EARLY_FLATTREE
int __init of_setup_earlycon(const struct earlycon_id *match,
                 unsigned long node,
                 const char *options)
struct uart_port *port = &early_console_dev.port;
port->iotype = UPIO_MEM;
port->mapbase = addr;
port->uartclk = BASE_BAUD * 16;
port->membase = earlycon_map(port->mapbase, SZ_4K);//获取寄存器地址
earlycon_init(&early_console_dev, match->name);
err = match->setup(&early_console_dev, options);
register_console(early_console_dev.con);

(1)从dtsi中获取到匹配节点,根据节点的内容来初始化early_console_dev.port
(2)earlycon_init继续对early_console_dev.port参数进行初始化。
(3)通过match->setup来设置write输出函数。
(4)最后,调用register_console向console子系统注册early_console_dev.con。(在“console篇进行说明”)
到此,printk就可以通过early_console_dev.con->write来进行输出log了,后面会继续说明。

early_console_dev定义如下:
drivers/tty/serial/earlycon.c

static struct console early_con = {
    .name =        "uart",        /* fixed up at earlycon registration */
    .flags =    CON_PRINTBUFFER | CON_BOOT,
    .index =    0,
};
static struct earlycon_device early_console_dev = {
    .con = &early_con,
};

CON_PRINTBUFFER标识,表示注册这个console的时候,需要把printk的buf中的log通过这个console进行输出。
CON_BOOT标识,表示这是一个boot console(bcon)。当启动过程了注册其他非boot console的时候,需要先卸载掉这个console。具体参考“console”系列文章。

4、match->setup
目的是定义early_console_dev.con的write函数,这个也是平台要实现的核心部分。
也就是在前面说过的定义earlycon_id过程中指定的setup函数,对于s5pv210来说,这个函数是

static int __init s5pv210_early_console_setup(struct earlycon_device *device,
                          const char *opt)
{
    device->port.private_data = &s5pv210_early_console_data;
    return samsung_early_console_setup(device, opt);
}
static int __init samsung_early_console_setup(struct earlycon_device *device,
                          const char *opt)
{
    if (!device->port.membase)
        return -ENODEV;

    device->con->write = samsung_early_write;
    return 0;
}

具体见后续“四、earlycon平台对应的实现位置”的说明.
到这里,earlycon就已经初始化完成了。

三、printk打印流程
当register_console(early_console_dev.con)完成之后,console子系统中的console_drivers就存在了early_console_dev.con这个console(具体参考“console”的文章)。
经过printk的标准调用之后

printk->vprintk->console_unlock->call_console_drivers
1
在call_console_drivers调用如下:

    for_each_console(con) {
        con->write(con, text, len);
    }
#define for_each_console(con) \
        for (con = console_drivers; con != NULL; con = con->next)

early_console_dev.con作为当前console_drivers一个con,其write函数也会被调用。
early_console_dev.con->write(con, text, len);
最终调用对应平台的console的write函数中。

四、earlycon平台对应的实现位置
s5pv210的early con实现流程:

1、dtis上需要定义compatiable和reg
s5pv210的对应dtsi节点:
arch/arm/boot/dts/s5pv210.dtsi

        uart0: serial@e2900000 {
            compatible = "samsung,s5pv210-uart";
            reg = <0xe2900000 0x400>;
        };

对于earlycon而言,只在乎compatible属性和reg属性,其他需要初始化的都要在uboot中完成。
compatible属性在earlycon的实现中用于和earlycon_id匹配。
reg属性则是整个uart寄存器的基地址和长度。(因为是和通用uart驱动共用的)。在要实现的write函数中,发送寄存器则是在这个基地址上进行偏移的。

2、代码中实现部分
drivers/tty/serial/samsung.c
需要打开如下宏:

#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE

代码中主要目的是实现对应的earlycon_id,而实现earlycon_id的主要工作又是实现其setup函数。实现setup函数的主要核心是实现uart的write函数。
(1)定义一个earlycon_id

OF_EARLYCON_DECLARE(s5pv210, "samsung,s5pv210-uart",
            s5pv210_early_console_setup);

注意:”samsung,s5pv210-uart”需要和dtsi里面console节点匹配的。

(2)实现setup函数。(主要目的是实现earlycon_device.con的write方法)

static int __init s5pv210_early_console_setup(struct earlycon_device *device,
                          const char *opt)
{
    device->port.private_data = &s5pv210_early_console_data;
    return samsung_early_console_setup(device, opt);
}

设置s5pv210的私有数据之后,调用samsung的通用的early_console_setup函数

static int __init samsung_early_console_setup(struct earlycon_device *device,
                          const char *opt)
{
    if (!device->port.membase)
        return -ENODEV;
    device->con->write = samsung_early_write;
    return 0;
}

samsung_early_write实现如下:

static void samsung_early_write(struct console *con, const char *s, unsigned n)
{
    struct earlycon_device *dev = con->data;
    uart_console_write(&dev->port, s, n, samsung_early_putc);
}

uart_console_write会将调用samsung_early_putc将字符串中的字符挨个处理。

static void samsung_early_putc(struct uart_port *port, int c)
{
    if (readl(port->membase + S3C2410_UFCON) & S3C2410_UFCON_FIFOMODE)
        samsung_early_busyuart_fifo(port);
    else
        samsung_early_busyuart(port);
    writeb(c, port->membase + S3C2410_UTXH);
}
#define S3C2410_UTXH      (0x20)

最终可以观察到uart成功输出log,并且有如下log:

earlycon: s5pv210 at MMIO 0xe2900000 (options '')
bootconsole [s5pv210] enabled
————————————————
版权声明:本文为CSDN博主「ooonebook」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ooonebook/article/details/52654191

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值