卢骏 · 2020年09月08日

arm64 earlycon分析

ARM64,在kernel未建立console之前,使用earlycon,实现打印。在bootargs中,要加入如下选项:

earlycon=pl011,0x70000000 

pl011表示针对pl011这个串口设备,0x70000000是串口的起始地址。

使用 amba-pl011串口,在 amba-pl011.c中,

有如下宏定义:

EARLYCON_DECLARE(pl011, pl011_early_console_setup) 

展开之后,

static const struct earlycon_id _earlycon_pl011  \
__used __section(__earlycon_table) \
= {.name = "pl011", setup=pl011_early_console_setup }; 

定义了一个earlycon_id结构的变量_earlycon_pl011。该变量中, setup函数指针指向pl011_early_console_setup函数。

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

在 earlycon.c(drivers/tty/serial)中,有如下宏定义:

early_param("earlycon", param_setup_earlycon); 

展开如下,定义一个变量,和一个结构体变量。结构体变量,放在了.init.setup段中。

s

tatic const char __setup_str_param_setup_earlycon \
   __initconst __aligned(1) = "earlycon";  \
static struct obs_kernel_param __setup_param_setup_earlycon  \
   __used __setion(.init.setup)       \
   __attrubite__((aligned(sizeof(long))))  \
   = { "earlycon", param_setup_earlycon, 1 }; 

obs_kernel_param原型:

struct obs_kernel_param {
  const char *str;
  int (*setup_func)(char *);
  int early;
} 

在 init/main.c中, start_kernel->setup_arch->parse_early_param,通过cmdline传递的参数,进行early初始化。

/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
  static int done __initdata;
  static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
  if (done)
   return;
 /* All fall through to do_early_param. */
 strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
 parse_early_options(tmp_cmdline);
 done = 1;
} 

通过parse_early_options函数,分析cmdline,也就是bootargs。

void __init parse_early_options(char *cmdline)

{
  parse_args("early options", cmdline, NULL, 0, 0, 0, NULL, do_early_param);
} 

调用parse_args,从cmdline中,分析early options。关键是do_early_param函数。参数param是cmdline中的参数变量以及参数值。


/* Check for early params. */

static int __init do_early_param(char *param, char *val,
   const char *unused, void *arg)
{
   const struct obs_kernel_param *p;
   for (p = __setup_start; p < __setup_end; p++) {
   if ((p->early && parameq(param, p->str)) ||
   (strcmp(param, "console") == 0 &&
   strcmp(p->str, "earlycon") == 0)
   ) {
   if (p->setup_func(val) != 0)
   pr_warn("Malformed early option '%s'\n", param);
   }
   }
   /* We accept everything at this stage. */

   return 0;

} 

这里的__setup_start,是链接脚本中的变量,定义如下,该变量是段.init.setup的起始地址,__setup_end是.init.setup段的结束地址。

.= ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .; 

do_early_param函数的for循环中,从.init.setup段中,依次将obs_kernel_param结构体变量取出来,如果变量中的early为1,并且变量中的str,和函数的参数一致,那么调用结构体中的setup_func函数。

在之前,__setup_param_setup_earlycon变量,是定义在.init.setup段。如下图所示。

1.png

因为cmdline中,传递了earlycon参数,匹配__setup_param_setup_earlycon中的earlycon,因此执行param_setup_earlycon函数。

在earlycon.c(drivers/tty/serial),有该函数,该函数调用setup_earlycon函数。

/* early_param wrapper for setup_earlycon() */
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 if (!buf) {
   return early_init_dt_scan_chosen_stdout();
   }
   }
   err = setup_earlycon(buf);
   if (err == -ENOENT || err == -EALREADY)
   return 0;
   return err;
} 

对于setup_earlycon函数,参数buf是cmdline的参数值。在这里是earlycon=pl011,0x70000000。

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->name[0]; 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;
} 

遍历__earlycon_table开始的earlycon_id类型的变量。

对于_earlycon_table,是定义在链接脚本中,保存__early_table段的起始地址。

__early_table = .; (__early_table) (__earlycon_table_end) . = ALIGN(8)

在之前,有定义_earlycon_pl011变量,并且,放在了__early_table段中。
2.png

cmdline传的参数是 earlycon=pl011,0x70000000,
参数值为pl011,0x70000000,逗号之前的pl011和_earlycon_pl011变量中的pl011匹配,因此执行register_earlycon函数。

该函数的2个参数,buf,是0x70000000,match是_earlycon_pl011变量的指针。

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;
} 

最终,调用match->setup函数,建立earlycon,其实就是调用pl011_early_console_setup。


/*

* On non-ACPI systems, earlycon is enabled by specifying
* "earlycon=pl011,<address>" on the kernel command line.
*
* On ACPI ARM64 systems, an "early" console is enabled via the SPCR table,
* by specifying only "earlycon" on the command line.  Because it requires
* SPCR, the console starts after ACPI is parsed, which is later than a
* traditional early console.
*
* To get the traditional early console that starts before ACPI is parsed,
* specify the full "earlycon=pl011,<address>" option.
*/
static int __init pl011_early_console_setup(struct earlycon_device *device, const char *opt)
{
   if (!device->port.membase)
   return -ENODEV;
   device->con->write = pl011_early_write;
   return 0;
} 

其实就是将设置write函数指针为pl011_early_write。这样,在kernel未建立console之前,使用printk打印的信息,最终是调用pl011_early_write函数输出了。

过程中遇到的问题:

  1. 在kernel未建立console之前,printk打印的信息是怎么输出?

对于ARM64,通过earlycon机制输出。

  1. 在kernel未初始化earlycon之前,printk打印的信息是怎么输出?

在未初始化earlycon之前,printk打印的信息,其实是没有打印出来的,打印信息保存在内部的缓冲区,等待earlycon建立好后,缓冲区的信息才被打印出来。

更多相关阅读

根文件系统 (一)根文件系统简介
根文件系统与ramdisk用
根文件系统(二)busybox构建根文件系统

原文首发于骏的世界博客
作者:卢骏
更多IC设计相关的文章请关注IC设计极术专栏,每日更新。

推荐阅读
关注数
20252
内容数
1308
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息