首先看到uart驱动probe的过程:
[ 0.675729] Serial: AMBA PL011 UART driver
[ 0.735090] 9000000.pl011: ttyAMA0 at MMIO 0x9000000 (irq = 39, base_baud = 0) is a PL011 rev1
[ 0.736770] [VUART_DBG] register_console:2656 ttyAMA
[ 0.737602] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.6.0-rc4-00002-g84594dce3dfa-dirty #20
[ 0.739146] Hardware name: linux,dummy-virt (DT)
[ 0.739867] Call trace:
[ 0.740335] dump_backtrace+0x0/0x1a0
[ 0.740922] show_stack+0x14/0x20
[ 0.741514] dump_stack+0xbc/0x104
[ 0.742046] register_console+0x38/0x3e0
[ 0.742707] uart_add_one_port+0x42c/0x500
[ 0.743016] pl011_register_port+0x68/0xd0
[ 0.743496] pl011_probe+0x144/0x198
[ 0.743730] amba_probe+0xbc/0x158
[ 0.743957] really_probe+0x108/0x360
[ 0.744292] driver_probe_device+0x58/0x100
[ 0.744578] __device_attach_driver+0x90/0xb0
[ 0.744866] bus_for_each_drv+0x64/0xc8
[ 0.745165] __device_attach+0xd8/0x138
[ 0.745864] device_initial_probe+0x10/0x18
[ 0.745995] bus_probe_device+0x90/0x98
[ 0.746178] device_add+0x4c4/0x770
[ 0.746288] amba_device_try_add+0x1b8/0x360
[ 0.746416] amba_device_add+0x18/0xd8
[ 0.751260] of_platform_bus_create+0x308/0x3b8
[ 0.753329] of_platform_populate+0x7c/0x108
[ 0.753473] of_platform_default_populate_init+0xb8/0xd4
[ 0.753638] do_one_initcall+0x5c/0x1b0
[ 0.753762] kernel_init_freeable+0x19c/0x204
[ 0.753902] kernel_init+0x10/0x108
[ 0.754016] ret_from_fork+0x10/0x18
[ 0.755131] printk: console [ttyAMA0] enabled
[ 0.755131] printk: console [ttyAMA0] enabled
[ 0.757799] printk: bootconsole [pl11] disabled
[ 0.757799] printk: bootconsole [pl11] disabled
在kernel_init中of_platform_default_populate_init展开初始化过程,amba总线开始遍历其驱动以及设备进行探测初始化,从而调用pl011_probe对设备进行初始化,对应的dts配置如下:
pl011@9000000 {
clock-names = "uartclk", "apb_pclk";
clocks = <0x8000 0x8000>;
interrupts = <0x0 0x1 0x4>;
reg = <0x0 0x9000000 0x0 0x1000>;
compatible = "arm,pl011", "arm,primecell";
};
而对应的console在chosen节点定义如下:
chosen {
linux,initrd-end = "H h";
linux,initrd-start = <0x48000000>;
bootargs = "root=/dev/ram rdinit=sbin/init console=ttyAMA0 ignore_loglevel earlycon";
stdout-path = "/pl011@9000000";
};
那么console=ttyAMA0以及pl011的定义以及driver的注册探测过程之间的关系如何呢?
在driver/tty/serial下的amba-pl011.c文件中定义了驱动模块:
static struct amba_driver pl011_driver = {
.drv = {
.name = "uart-pl011",
.pm = &pl011_dev_pm_ops,
.suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_AMBA_PL011),
},
.id_table = pl011_ids,
.probe = pl011_probe,
.remove = pl011_remove,
};
static int __init pl011_init(void)
{
printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
if (platform_driver_register(&arm_sbsa_uart_platform_driver))
pr_warn("could not register SBSA UART platform driver\n");
return amba_driver_register(&pl011_driver);
}
static void __exit pl011_exit(void)
{
platform_driver_unregister(&arm_sbsa_uart_platform_driver);
amba_driver_unregister(&pl011_driver);
}
/*
* While this can be a module, if builtin it's most likely the console
* So let's leave module_exit but move module_init to an earlier place
*/
arch_initcall(pl011_init);
module_exit(pl011_exit);
这里驱动模块采用了arch_initcall,相对module_init较早加载,在amba_driver_register函数中对pl011_driver.drv->bus赋值:
drv->drv.bus = &amba_bustype;
而在probe阶段需要做的match由name决定,上面定义中设定为"uart-pl011",从而amba_bustype上面存在名为uart-pl011的驱动。
首先直接来看到dts中设备节点的解析过程中最重要的一个中间函数:
/**
* of_platform_bus_create() - Create a device for a node and its children.
* @bus: device node of the bus to instantiate
* @matches: match table for bus nodes
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent for new device, or NULL for top level.
* @strict: require compatible property
*
* Creates a platform_device for the provided device_node, and optionally
* recursively create devices for all the child nodes.
*/
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %pOF, no compatible prop\n",
__func__, bus);
return 0;
}
/* Skip nodes for which we don't want to create devices */
if (unlikely(of_match_node(of_skipped_node_table, bus))) {
pr_debug("%s() - skipping %pOF node\n", __func__, bus);
return 0;
}
if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
pr_debug("%s() - skipping %pOF, already populated\n",
__func__, bus);
return 0;
}
auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}
if (of_device_is_compatible(bus, "arm,primecell")) {
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node(bus, child) {
pr_debug(" create child: %pOF\n", child);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}
在这里给每个节点及其所有子节点创建device,而其中amba对应的部分为经过of_device_is_compatible(bus, "arm,primecell")匹配之后调用的of_amba_device_create(bus, bus_id, platform_data, parent);而对amba设备的操作过程调用路径如下:
of_amba_device_create->amba_device_add->amba_device_try_add->device_add->bus_probe_device->device_initial_probe->__device_attach->bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver)->driver_probe_device->really_probe->dev->bus->probe(dev)->amba_probe-> 调用to_amba_driver(dev->driver)->probe函数,->pl011_probe;
最终调用到设备驱动的probe函数pl011_probe进入到前面提到的设备驱动模块。
同样首先看函数调用过程:
uart_configure_port->pl011_register_port->uart_add_one_port->uart_configure_port->register_console
最终将设备注册到console_drivers链表中,下面分析为什么它被设置为默认的console设备。
在dts配置中chosen节点我们配置了bootargs,其中有console配置信息,其解析过程定义在printk.c文件中(kernel/printk路径):
static int __init console_setup(char *str)
{
char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for "ttyS" */
char *s, *options, *brl_options = NULL;
int idx;
if (_braille_console_setup(&str, &brl_options))
return 1;
/*
* Decode str into name, index, options.
*/
if (str[0] >= '0' && str[0] <= '9') {
strcpy(buf, "ttyS");
strncpy(buf + 4, str, sizeof(buf) - 5);
} else {
strncpy(buf, str, sizeof(buf) - 1);
}
buf[sizeof(buf) - 1] = 0;
options = strchr(str, ',');
if (options)
*(options++) = 0;
#ifdef __sparc__
if (!strcmp(str, "ttya"))
strcpy(buf, "ttyS0");
if (!strcmp(str, "ttyb"))
strcpy(buf, "ttyS1");
#endif
for (s = buf; *s; s++)
if (isdigit(*s) || *s == ',')
break;
idx = simple_strtoul(s, NULL, 10);
*s = 0;
__add_preferred_console(buf, idx, options, brl_options);
console_set_on_cmdline = 1;
return 1;
}
__setup("console=", console_setup);
可以看到以内核__setup的方式定义其解析函数,在内核启动参数解析过程中调用解析函数console_setup设置console。其主要过程为调用__add_preferred_console函数,对console_drivers遍历,将其设备名称与命令行配置的设备名称进行匹配,如果匹配到则说明该console设备已经注册,将其标注为默认的console,标注方法是将其序号赋值给preferred_console。而如果遍历当前已经注册的console链表未找到匹配的设备且还未超出最大可允许注册的console数目,则将链表下一个元素标记为默认,并将命令行参数对应设备名称赋值到该链表元素中,在设备注册的时候完成对该元素的完整初始化即可,详情如下:
static int __add_preferred_console(char *name, int idx, char *options,
char *brl_options)
{
struct console_cmdline *c;
int i;
/*
* See if this tty is not yet registered, and
* if we have a slot free.
*/
for (i = 0, c = console_cmdline;
i < MAX_CMDLINECONSOLES && c->name[0];
i++, c++) {
if (strcmp(c->name, name) == 0 && c->index == idx) {
if (!brl_options)
preferred_console = i;
return 0;
}
}
if (i == MAX_CMDLINECONSOLES)
return -E2BIG;
if (!brl_options)
preferred_console = i;
strlcpy(c->name, name, sizeof(c->name));
c->options = options;
braille_set_options(c, brl_options);
c->index = idx;
return 0;
}
至此,我们可以看到三者的关联,从总线驱动注册到设备在总线上被探测和初始化,然后注册对应的console设备,而命令行参数解析过程结合起来将之后确定了该设备为默认console。