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=ttyAMBA0以及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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章