当在kernel commandline 传递earlycon后系统对earlycon的解析是在drivers/tty/serial/earlycon.c 中
static int __init param_setup_earlycon(char *buf)
{
int err;
/*
* Just 'earlycon' is a valid param for devicetree earlycons;
* don't generate a warning from parse_early_params() in that case
*/
if (!buf || !buf[0]) {
if (IS_ENABLED(CONFIG_ACPI_SPCR_TABLE)) {
earlycon_init_is_deferred = true;
return 0;
} else {
return early_init_dt_scan_chosen_stdout();
}
}
err = setup_earlycon(buf);
if (err == -ENOENT || err == -EALREADY)
return 0;
return err;
}
early_param("earlycon", param_setup_earlycon);
当buf不为0,也就是earlycon后面会跟一串设定,例如earlycon=pl011,mmio,0x12345678. 这个时候就会调用setup_earlycon
int __init setup_earlycon(char *buf)
{
const struct earlycon_id *match;
if (!buf || !buf[0])
return -EINVAL;
if (early_con.flags & CON_ENABLED)
return -EALREADY;
for (match = __earlycon_table; match < __earlycon_table_end; match++) {
size_t len = strlen(match->name);
if (strncmp(buf, match->name, len))
continue;
if (buf[len]) {
if (buf[len] != ',')
continue;
buf += len + 1;
} else
buf = NULL;
return register_earlycon(buf, match);
}
return -ENOENT;
}
setup_earlycon 中遍历__earlycon_table到__earlycon_table_end,之间找到和kernel commandline 匹配的,例如我们这里就是匹配pl011 这个字符串。如果匹配到了就调用register_earlycon
static int __init register_earlycon(char *buf, const struct earlycon_id *match)
{
int err;
struct uart_port *port = &early_console_dev.port;
/* On parsing error, pass the options buf to the setup function */
if (buf && !parse_options(&early_console_dev, buf))
buf = NULL;
spin_lock_init(&port->lock);
port->uartclk = BASE_BAUD * 16;
if (port->mapbase)
port->membase = earlycon_map(port->mapbase, 64);
earlycon_init(&early_console_dev, match->name);
err = match->setup(&early_console_dev, buf);
if (err < 0)
return err;
if (!early_console_dev.con->write)
return -ENODEV;
register_console(early_console_dev.con);
return 0;
}
在register_earlycon 中会调用
static void __init earlycon_init(struct earlycon_device *device,
const char *name)
{
struct console *earlycon = device->con;
struct uart_port *port = &device->port;
const char *s;
size_t len;
/* scan backwards from end of string for first non-numeral */
for (s = name + strlen(name);
s > name && s[-1] >= '0' && s[-1] <= '9';
s--)
;
if (*s)
earlycon->index = simple_strtoul(s, NULL, 10);
len = s - name;
strlcpy(earlycon->name, name, min(len + 1, sizeof(earlycon->name)));
earlycon->data = &early_console_dev;
if (port->iotype == UPIO_MEM || port->iotype == UPIO_MEM16 ||
port->iotype == UPIO_MEM32 || port->iotype == UPIO_MEM32BE)
pr_info("%s%d at MMIO%s %pa (options '%s')\n",
earlycon->name, earlycon->index,
(port->iotype == UPIO_MEM) ? "" :
(port->iotype == UPIO_MEM16) ? "16" :
(port->iotype == UPIO_MEM32) ? "32" : "32be",
&port->mapbase, device->options);
else
pr_info("%s%d at I/O port 0x%lx (options '%s')\n",
earlycon->name, earlycon->index,
port->iobase, device->options);
}
在earlycon_init 中有一个关键的赋值earlycon->data = &early_console_dev;
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_BOOT 这个flag是在这里赋值的
回到register_earlycon 中继续调用 err = match->setup(&early_console_dev, buf);针对本例,
OF_EARLYCON_DECLARE(pl011, "arm,pl011", pl011_early_console_setup);
而#define OF_EARLYCON_DECLARE(_name, compat, fn) \
static const struct earlycon_id __UNIQUE_ID(__earlycon_##_name) \
EARLYCON_USED_OR_UNUSED __section(__earlycon_table) \
= { .name = __stringify(_name), \
.compatible = compat, \
.setup = fn }
可见是通过OF_EARLYCON_DECLARE将pl011_early_console_setup 放到__earlycon_table到__earlycon_table_end 之间的,所以这里对应的setup函数为pl011_early_console_setup
static int __init pl011_early_console_setup(struct earlycon_device *device,
const char *opt)
{
if (!device->port.membase)
return -ENODEV;
pl011_check_busy_workaround();
device->con->write = pl011_early_write;
return 0;
}
从pl011_early_console_setup 中可以得知device->con->write指向的函数为pl011_early_write
static void pl011_early_write(struct console *con, const char *s, unsigned n)
{
struct earlycon_device *dev = con->data;
uart_console_write(&dev->port, s, n, pl011_putc);
}
在uart_console_write 中最终调用pl011_putc 来将字符串显示到串口上
static void pl011_putc(struct uart_port *port, int c)
{
while (readl(port->membase + UART01x_FR) & UART01x_FR_TXFF)
cpu_relax();
if (port->iotype == UPIO_MEM32)
writel(c, port->membase + UART01x_DR);
else
writeb(c, port->membase + UART01x_DR);
if (unlikely(pl011_workaround_busy)) {
while (!(readl(port->membase + UART01x_FR) & UART011_FR_TXFE))
cpu_relax();
} else {
while (readl(port->membase + UART01x_FR) & UART01x_FR_BUSY)
cpu_relax();
}
}
从pl011_putc 实现可以看到实际就是直接通过writel来写寄存器而已.
所以从earlycon的实现可以知道,earlycon就是直接写寄存器而已,所以earlycon的flag中包含CON_BOOT 这个flag。所以当真正的console使用DMA输出字符的时候,会将包含CON_BOOT的console卸载掉
if (bcon &&
((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
!keep_bootcon) {
/* We need to iterate through all boot consoles, to make
* sure we print everything out, before we unregister them.
*/
for_each_console(bcon)
if (bcon->flags & CON_BOOT)
unregister_console(bcon);
}
这段code是register_console 中卸载包含CON_BOOT 这个flag。
static int __init param_setup_earlycon(char *buf)
{
int err;
/*
* Just 'earlycon' is a valid param for devicetree earlycons;
* don't generate a warning from parse_early_params() in that case
*/
if (!buf || !buf[0]) {
if (IS_ENABLED(CONFIG_ACPI_SPCR_TABLE)) {
earlycon_init_is_deferred = true;
return 0;
} else {
return early_init_dt_scan_chosen_stdout();
}
}
err = setup_earlycon(buf);
if (err == -ENOENT || err == -EALREADY)
return 0;
return err;
}
early_param("earlycon", param_setup_earlycon);
当buf不为0,也就是earlycon后面会跟一串设定,例如earlycon=pl011,mmio,0x12345678. 这个时候就会调用setup_earlycon
int __init setup_earlycon(char *buf)
{
const struct earlycon_id *match;
if (!buf || !buf[0])
return -EINVAL;
if (early_con.flags & CON_ENABLED)
return -EALREADY;
for (match = __earlycon_table; match < __earlycon_table_end; match++) {
size_t len = strlen(match->name);
if (strncmp(buf, match->name, len))
continue;
if (buf[len]) {
if (buf[len] != ',')
continue;
buf += len + 1;
} else
buf = NULL;
return register_earlycon(buf, match);
}
return -ENOENT;
}
setup_earlycon 中遍历__earlycon_table到__earlycon_table_end,之间找到和kernel commandline 匹配的,例如我们这里就是匹配pl011 这个字符串。如果匹配到了就调用register_earlycon
static int __init register_earlycon(char *buf, const struct earlycon_id *match)
{
int err;
struct uart_port *port = &early_console_dev.port;
/* On parsing error, pass the options buf to the setup function */
if (buf && !parse_options(&early_console_dev, buf))
buf = NULL;
spin_lock_init(&port->lock);
port->uartclk = BASE_BAUD * 16;
if (port->mapbase)
port->membase = earlycon_map(port->mapbase, 64);
earlycon_init(&early_console_dev, match->name);
err = match->setup(&early_console_dev, buf);
if (err < 0)
return err;
if (!early_console_dev.con->write)
return -ENODEV;
register_console(early_console_dev.con);
return 0;
}
在register_earlycon 中会调用
static void __init earlycon_init(struct earlycon_device *device,
const char *name)
{
struct console *earlycon = device->con;
struct uart_port *port = &device->port;
const char *s;
size_t len;
/* scan backwards from end of string for first non-numeral */
for (s = name + strlen(name);
s > name && s[-1] >= '0' && s[-1] <= '9';
s--)
;
if (*s)
earlycon->index = simple_strtoul(s, NULL, 10);
len = s - name;
strlcpy(earlycon->name, name, min(len + 1, sizeof(earlycon->name)));
earlycon->data = &early_console_dev;
if (port->iotype == UPIO_MEM || port->iotype == UPIO_MEM16 ||
port->iotype == UPIO_MEM32 || port->iotype == UPIO_MEM32BE)
pr_info("%s%d at MMIO%s %pa (options '%s')\n",
earlycon->name, earlycon->index,
(port->iotype == UPIO_MEM) ? "" :
(port->iotype == UPIO_MEM16) ? "16" :
(port->iotype == UPIO_MEM32) ? "32" : "32be",
&port->mapbase, device->options);
else
pr_info("%s%d at I/O port 0x%lx (options '%s')\n",
earlycon->name, earlycon->index,
port->iobase, device->options);
}
在earlycon_init 中有一个关键的赋值earlycon->data = &early_console_dev;
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_BOOT 这个flag是在这里赋值的
回到register_earlycon 中继续调用 err = match->setup(&early_console_dev, buf);针对本例,
OF_EARLYCON_DECLARE(pl011, "arm,pl011", pl011_early_console_setup);
而#define OF_EARLYCON_DECLARE(_name, compat, fn) \
static const struct earlycon_id __UNIQUE_ID(__earlycon_##_name) \
EARLYCON_USED_OR_UNUSED __section(__earlycon_table) \
= { .name = __stringify(_name), \
.compatible = compat, \
.setup = fn }
可见是通过OF_EARLYCON_DECLARE将pl011_early_console_setup 放到__earlycon_table到__earlycon_table_end 之间的,所以这里对应的setup函数为pl011_early_console_setup
static int __init pl011_early_console_setup(struct earlycon_device *device,
const char *opt)
{
if (!device->port.membase)
return -ENODEV;
pl011_check_busy_workaround();
device->con->write = pl011_early_write;
return 0;
}
从pl011_early_console_setup 中可以得知device->con->write指向的函数为pl011_early_write
static void pl011_early_write(struct console *con, const char *s, unsigned n)
{
struct earlycon_device *dev = con->data;
uart_console_write(&dev->port, s, n, pl011_putc);
}
在uart_console_write 中最终调用pl011_putc 来将字符串显示到串口上
static void pl011_putc(struct uart_port *port, int c)
{
while (readl(port->membase + UART01x_FR) & UART01x_FR_TXFF)
cpu_relax();
if (port->iotype == UPIO_MEM32)
writel(c, port->membase + UART01x_DR);
else
writeb(c, port->membase + UART01x_DR);
if (unlikely(pl011_workaround_busy)) {
while (!(readl(port->membase + UART01x_FR) & UART011_FR_TXFE))
cpu_relax();
} else {
while (readl(port->membase + UART01x_FR) & UART01x_FR_BUSY)
cpu_relax();
}
}
从pl011_putc 实现可以看到实际就是直接通过writel来写寄存器而已.
所以从earlycon的实现可以知道,earlycon就是直接写寄存器而已,所以earlycon的flag中包含CON_BOOT 这个flag。所以当真正的console使用DMA输出字符的时候,会将包含CON_BOOT的console卸载掉
if (bcon &&
((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
!keep_bootcon) {
/* We need to iterate through all boot consoles, to make
* sure we print everything out, before we unregister them.
*/
for_each_console(bcon)
if (bcon->flags & CON_BOOT)
unregister_console(bcon);
}
这段code是register_console 中卸载包含CON_BOOT 这个flag。