early_console是Kernel初始化初期建立起來用於串行輸出的設備,源碼在earlycon.c中實現。
1. 初始化入口
Kernel中 early_param 通常作爲模塊初始化入口元素的定義,在Kernel初始化時執行解析和模塊初始化。Linux Kernel中衆多模塊或驅動子系統均通過這種方式定義其初始化入口。在Kernel初始化彙編代碼執行完跳轉到start_kernel之後,setup_arch調用parse_early_param,進而在其中執行 early_param 的解析,具體如下:
start_kernel->setup_arch->parse_early_param->parse_early_options->do_early_param
實際上在parse_early_options中調用parse_args,並且將do_early_param作爲參數傳入,進而繼續調用parse_one且傳入參數do_early_param,parse_one將參數解析成功後執行do_early_param:
/* 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;
}
在vmlinux.lds.h(include/asm-generic)中可以看到對於.init.setup段的定義:
#define INIT_SETUP(initsetup_align) \
. = ALIGN(initsetup_align); \
__setup_start = .; \
KEEP(*(.init.setup)) \
__setup_end = .;
其對__setup_start和__setup_end地址也做了劃定,那麼此處的do_early_param函數就是對.init.setup段內定義的參數進行遍歷並調用器setup_func成員進行初始化。
而earlycon的early_param的定義如下:
/* early_param wrapper for setup_earlycon() */
static int __init param_setup_earlycon(char *buf)
{
int err;
/* Just 'earlycon' is a valid param for devicetree and ACPI SPCR. */
if (!buf || !buf[0]) {
if (IS_ENABLED(CONFIG_ACPI_SPCR_TABLE)) {
earlycon_acpi_spcr_enable = 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;
}
early_param("earlycon", param_setup_earlycon);
這裏通過early_param來定義,需要先分析一下early_param:
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
/*
* Only for really core code. See moduleparam.h for the normal way.
*
* Force the alignment so the compiler doesn't space elements of the
* obs_kernel_param "array" too far apart in .init.setup.
*/
#define __setup_param(str, unique_id, fn, early) \
static const char __setup_str_##unique_id[] __initconst \
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id \
__used __section(.init.setup) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
/*
* NOTE: fn is as per module_param, not __setup!
* Emits warning if fn returns non-zero.
*/
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
就是一系列的宏,展開如下:
early_param("earlycon", param_setup_earlycon);
__setup_param("earlycon", param_setup_earlycon, param_setup_earlycon, 1);
static const char __setup_str_param_setup_earlycon[] __initconst __aligned(1) = "earlycon";
static struct obs_kernel_param __setup_param_setup_earlycon
__used_section(.init.setup) __attribute__((aligned((sizeof(long)))))
= {
.str = __setup_str_param_setup_earlycon,
.setup_func = param_setup_earlycon,
.early = 1
};
通過展開可以看出其實際作用是定義了一個類型爲struct obs_kernel_param的靜態結構並賦予初值,並且使用了編譯器選項通知編譯器在編譯階段將變量放置在.init.setup段,在上面提到的do_early_param解析過程中解析,並執行setup_func。
如上遍歷執行p->setup_func(val)時,如果遍歷到__setup_param_setup_earlycon元素,則p實際上是指針,指向&__setup_param_setup_earlycon, 即該結構的地址,執行其setup_func函數,即param_setup_earlycon。
通過上面的分析可以看到,在start_kernel調用setup_arch函數,進而執行啓動參數(boot_command_line)解析,如果存在參數“earlycon”,則 param_setup_earlycon 函數被執行,正式進入early_console初始化過程,該函數其實是一個包裹函數,其中進行兩種解析過程,第一種是early_init_dt_scan_chosen_stdout函數中進行的根據device-tree解析進行,另一種則通過 setup_earlycon 函數。
這裏提到了Kernel啓動參數boot_command_line,有讀者對這部分感興趣的話可以閱讀另一篇博文《Kernel啓動參數boot_command_line》。
2. 通過setup_earlycon初始化earlycon
執行過程如下:
early_param: param_setup_earlycon->setup_earlycon->register_earlycon->earlycon_map; earlycon_init; register_console
/**
* setup_earlycon - match and register earlycon console
* @buf: earlycon param string
*
* Registers the earlycon console matching the earlycon specified
* in the param string @buf. Acceptable param strings are of the form
* <name>,io|mmio|mmio32|mmio32be,<addr>,<options>
* <name>,0x<addr>,<options>
* <name>,<options>
* <name>
*
* Only for the third form does the earlycon setup() method receive the
* <options> string in the 'options' parameter; all other forms set
* the parameter to NULL.
*
* Returns 0 if an attempt to register the earlycon was made,
* otherwise negative error code
*/
int __init setup_earlycon(char *buf)
{
const struct earlycon_id **p_match;
if (!buf || !buf[0])
return -EINVAL;
if (early_con.flags & CON_ENABLED)
return -EALREADY;
for (p_match = __earlycon_table; p_match < __earlycon_table_end;
p_match++) {
const struct earlycon_id *match = *p_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設備爲early_console_dev,定義在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,
};
register_earlycon函數中通過parse_options函數進行命令行參數解析,獲取到console的各項配置參數,如port地址信息等,通過 earlycon_map; earlycon_init;函數將獲取到的參數實際進行賦值,完成初始化,再通過 register_console註冊。
3. 通過命令行參數定義earlycon
參考Documentation/kernel-parameters.txt和Documentation/serial-console.txt中對console和earlycon的說明。
4. 通過early_init_dt_scan_chosen_stdout初始化earlycon
函數定義如下:
#ifdef CONFIG_SERIAL_EARLYCON
int __init early_init_dt_scan_chosen_stdout(void)
{
int offset;
const char *p, *q, *options = NULL;
int l;
const struct earlycon_id **p_match;
const void *fdt = initial_boot_params;
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 (p_match = __earlycon_table; p_match < __earlycon_table_end;
p_match++) {
const struct earlycon_id *match = *p_match;
if (!match->compatible[0])
continue;
if (fdt_node_check_compatible(fdt, offset, match->compatible))
continue;
if (of_setup_earlycon(match, offset, options) == 0)
return 0;
}
return -ENODEV;
}
#endif
該函數對device-tree中的chosen節點進行解析,獲取到stdout-path內容後對__earlycon_table段元素定義進行遍歷檢查和匹配,從而調用of_setup_earlycon函數進行earlycon的初始化:
#ifdef CONFIG_OF_EARLY_FLATTREE
int __init of_setup_earlycon(const struct earlycon_id *match,
unsigned long node,
const char *options)
{
int err;
struct uart_port *port = &early_console_dev.port;
const __be32 *val;
bool big_endian;
u64 addr;
spin_lock_init(&port->lock);
port->iotype = UPIO_MEM;
addr = of_flat_dt_translate_address(node);
if (addr == OF_BAD_ADDR) {
pr_warn("[%s] bad address\n", match->name);
return -ENXIO;
}
port->mapbase = addr;
val = of_get_flat_dt_prop(node, "reg-offset", NULL);
if (val)
port->mapbase += be32_to_cpu(*val);
port->membase = earlycon_map(port->mapbase, SZ_4K);
val = of_get_flat_dt_prop(node, "reg-shift", NULL);
if (val)
port->regshift = be32_to_cpu(*val);
big_endian = of_get_flat_dt_prop(node, "big-endian", NULL) != NULL ||
(IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) &&
of_get_flat_dt_prop(node, "native-endian", NULL) != NULL);
val = of_get_flat_dt_prop(node, "reg-io-width", NULL);
if (val) {
switch (be32_to_cpu(*val)) {
case 1:
port->iotype = UPIO_MEM;
break;
case 2:
port->iotype = UPIO_MEM16;
break;
case 4:
port->iotype = (big_endian) ? UPIO_MEM32BE : UPIO_MEM32;
break;
default:
pr_warn("[%s] unsupported reg-io-width\n", match->name);
return -EINVAL;
}
}
val = of_get_flat_dt_prop(node, "current-speed", NULL);
if (val)
early_console_dev.baud = be32_to_cpu(*val);
val = of_get_flat_dt_prop(node, "clock-frequency", NULL);
if (val)
port->uartclk = be32_to_cpu(*val);
if (options) {
early_console_dev.baud = simple_strtoul(options, NULL, 0);
strlcpy(early_console_dev.options, options,
sizeof(early_console_dev.options));
}
earlycon_init(&early_console_dev, match->name);
err = match->setup(&early_console_dev, options);
if (err < 0)
return err;
if (!early_console_dev.con->write)
return -ENODEV;
register_console(early_console_dev.con);
return 0;
}
#endif /* CONFIG_OF_EARLY_FLATTREE */
可以看到與前面提到通過 setup_earlycon 函數進行初始化過程不同,當param_setup_earlycon參數buf中並未傳入console參數,則通過這種方式進行初始化,當然是解析dts來獲取對應的配置參數,完成類似前面提到的一系列初始化過程,最後註冊console信息到console_drivers全局鏈表中。
5. 另一種定義方式——EARLYCON_DECLARE系列宏
可以通過EARLYCON_DECLARE宏來進行earlycon定義,比如著名的uart8250,其定義在8250_early.c(drivers/tty/serial/8250):
int __init early_serial8250_setup(struct earlycon_device *device,
const char *options)
{
if (!(device->port.membase || device->port.iobase))
return -ENODEV;
if (!device->baud) {
struct uart_port *port = &device->port;
unsigned int ier;
/* assume the device was initialized, only mask interrupts */
ier = serial8250_early_in(port, UART_IER);
serial8250_early_out(port, UART_IER, ier & UART_IER_UUE);
} else
init_port(device);
device->con->write = early_serial8250_write;
return 0;
}
EARLYCON_DECLARE(uart8250, early_serial8250_setup);
EARLYCON_DECLARE(uart, early_serial8250_setup);
OF_EARLYCON_DECLARE(ns16550, "ns16550", early_serial8250_setup);
OF_EARLYCON_DECLARE(ns16550a, "ns16550a", early_serial8250_setup);
OF_EARLYCON_DECLARE(uart, "nvidia,tegra20-uart", early_serial8250_setup);
OF_EARLYCON_DECLARE(uart, "snps,dw-apb-uart", early_serial8250_setup);
繼續分析EARLYCON_DECLARE,可以看到其定義如下:
/*
* Console helpers.
*/
struct earlycon_device {
struct console *con;
struct uart_port port;
char options[16]; /* e.g., 115200n8 */
unsigned int baud;
};
struct earlycon_id {
char name[15];
char name_term; /* In case compiler didn't '\0' term name */
char compatible[128];
int (*setup)(struct earlycon_device *, const char *options);
};
extern const struct earlycon_id *__earlycon_table[];
extern const struct earlycon_id *__earlycon_table_end[];
#if defined(CONFIG_SERIAL_EARLYCON) && !defined(MODULE)
#define EARLYCON_USED_OR_UNUSED __used
#else
#define EARLYCON_USED_OR_UNUSED __maybe_unused
#endif
#define _OF_EARLYCON_DECLARE(_name, compat, fn, unique_id) \
static const struct earlycon_id unique_id \
EARLYCON_USED_OR_UNUSED __initconst \
= { .name = __stringify(_name), \
.compatible = compat, \
.setup = fn }; \
static const struct earlycon_id EARLYCON_USED_OR_UNUSED \
__section(__earlycon_table) \
* const __PASTE(__p, unique_id) = &unique_id
#define OF_EARLYCON_DECLARE(_name, compat, fn) \
_OF_EARLYCON_DECLARE(_name, compat, fn, \
__UNIQUE_ID(__earlycon_##_name))
#define EARLYCON_DECLARE(_name, fn) OF_EARLYCON_DECLARE(_name, "", fn)
這裏看到了__UNIQUE_ID宏定義,有三個不同的版本,gcc版本定義如下:
/* 這裏用到了__PASTE宏,同樣貼出來,其實就是簡單的粘連 */
/* Indirect macros required for expanded argument pasting, eg. __LINE__. */
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)
爲了保證ID的唯一性,GCC預定義了__COUNTER__宏,編譯過程中對其遞增後展開,那麼我們對uart8250定義展開如下:
EARLYCON_DECLARE(uart8250, early_serial8250_setup);
OF_EARLYCON_DECLARE(uart8250, "", early_serial8250_setup);
_OF_EARLYCON_DECLARE(uart8250, "", early_serial8250_setup, \
__PASTE(__PASTE(__UNIQUE_ID_, __earlycon_uart8250), __COUNTER__));
/* 此處我們假設 __COUNTER__ 是第二次出現,其展開爲1 */
_OF_EARLYCON_DECLARE(uart8250, "", early_serial8250_setup, __UNIQUE_ID___earlycon_uart82501)
static const struct earlycon_id __UNIQUE_ID___earlycon_uart82501
EARLYCON_USED_OR_UNUSED __initconst
= {
.name = __stringify(uart8250),
.compatible = "",
.setup = early_serial8250_setup
};
static const struct earlycon_id
EARLYCON_USED_OR_UNUSED __section(__earlycon_table)
* const __punique_id = &__UNIQUE_ID___earlycon_uart82501;
如上最後的展開結果看到在__earlycon_table段定義了struct earlycon_id類型的指針,指向前面靜態定義的結構,其初始化函數爲early_serial8250_setup。那麼問題又來了,該table中定義的元素何時用到並執行其初始化函數完成console的初始化過程呢?
首先來看__eatlycon_table定義,同樣在vmlinux.lds.h文件中:
#ifdef CONFIG_SERIAL_EARLYCON
#define EARLYCON_TABLE() . = ALIGN(8); \
__earlycon_table = .; \
KEEP(*(__earlycon_table)) \
__earlycon_table_end = .;
#else
#define EARLYCON_TABLE()
#endif
對於定義在該section中earlycon元素的解析和初始化發生在setup_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;
}
/**
* setup_earlycon - match and register earlycon console
* @buf: earlycon param string
*
* Registers the earlycon console matching the earlycon specified
* in the param string @buf. Acceptable param strings are of the form
* <name>,io|mmio|mmio32|mmio32be,<addr>,<options>
* <name>,0x<addr>,<options>
* <name>,<options>
* <name>
*
* Only for the third form does the earlycon setup() method receive the
* <options> string in the 'options' parameter; all other forms set
* the parameter to NULL.
*
* Returns 0 if an attempt to register the earlycon was made,
* otherwise negative error code
*/
int __init setup_earlycon(char *buf)
{
const struct earlycon_id **p_match;
if (!buf || !buf[0])
return -EINVAL;
if (early_con.flags & CON_ENABLED)
return -EALREADY;
for (p_match = __earlycon_table; p_match < __earlycon_table_end;
p_match++) {
const struct earlycon_id *match = *p_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;
}
其實對於buf中的帶解析參數,與所有在該section中的earlycon_id元素進行name的對比,匹配到之後調用register_earlycon函數進行具體的初始化和註冊操作:在該函數內執行了parse_options進行參數解析,進一步調用earlycon_map進行端口地址映射,然後在earlycon_init進一步進行初始化,最終經過對應的setup函數配置過程之後調用register_console將對應的console註冊到console_drivers全局鏈表中。