ARM平台AMBA总线uart驱动和console初始化

1. 函数调用路径

首先看到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的注册探测过程之间的关系如何呢?

2. 驱动pl011注册过程

在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的驱动。

3. 设备节点的初始化

首先直接来看到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进入到前面提到的设备驱动模块。

4. 从AMBA设备驱动到console设备

同样首先看函数调用过程: 

uart_configure_port->pl011_register_port->uart_add_one_port->uart_configure_port->register_console

最终将设备注册到console_drivers链表中,下面分析为什么它被设置为默认的console设备。

5. 默认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。

你可能感兴趣的:(Linux,Driver,console,uart,arm,amba,linux)