本文以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