Linux 設備樹詳解【轉】

轉自:http://www.pianshen.com/article/428276673/;jsessionid=D90FC6B215155680E0B89A6D060892D4node

本文基於天嵌E9V3開發板,詳解設備樹的規則和用法。linux

1、基本概念

DTS即Device Tree Source,是一個文本形式的文件,用於描述硬件信息,包括CPU的數量和類別、內存基地址和大小、中斷控制器、總線和橋、外設、時鐘和GPIO控制器等。
DTB即Device Tree Blob,是一個二進制形式的文件,由linux內核識別,爲其中的設備匹配合適的驅動程序。
DTC即Device Tree Compiler,將適合人類閱讀和編輯的DTS文件編譯成適合機器處理的DTB文件。
編譯內核的時候會同時使用DTC 將DTS編譯成DTB,天嵌E9V3使用的DTS文件e9v3-sabresd.dts位於/arch/arm/boot/dts目錄下。
在這裏插入圖片描述
如上圖所示,bootloader讀取dtb文件放入RAM中,並將存放地址告訴linux內核,內核啓動之後從該地址讀取相應的設備信息,匹配平臺和設備驅動。git

2、E9V3設備樹總覽

linux中的一個dts文件對應一個machine, 不一樣的machine可能使用相同的SOC,只是對外設的使用不一樣,這些不一樣的dts文件勢必包含不少相同的內容,爲了簡化,能夠把公用的部分提煉爲dtsi文件。
e9v3-sabresd.dts包含dtsi的結構以下:
在這裏插入圖片描述github

列出各個文件中的節點,以下圖所示,是否是有點像有不少分支的樹?
在這裏插入圖片描述markdown

3、設備樹編寫規則

Device Tree的編寫規則可參考文檔<<devicetree-specification-v0.2.pdf>>, 如下簡稱spec,下載連接爲:
https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.2session

設備樹由一個一個的節點組成,每一個設備樹有且僅有一個根節點,節點能夠包含子節點。ide

一、節點名稱
基本的節點名格式以下:
node-name@unit-address
其中node-name由字母、數字和一些特殊字符構成的字符串,長度不超過31個字符,可自定義,但爲了可讀性,spec中規定了一些約定成熟的名稱,好比cpus, memory, bus,clock等。
unit-address爲節點的地址,一般爲寄存器的首地址,好比imx6q datasheet中uart1的寄存器地址範圍爲0202_0000~0202_3FFF,在定義uart1節點時,對應的unit-address爲0202_0000:
uart1: serial@02020000 {

}
有些節點沒有對應的寄存器,則unit-address可省略,節點名只由node-name組成,好比cpus:
cpus {

}
根節點的名稱比較特殊,由一個斜槓組成:
/{

}函數

二、label標籤atom

3、設備與驅動的匹配

linux內核啓動之後,先解析並註冊dts中的設備,而後再註冊驅動,比較驅動中的compatible 屬性和設備中的compatible 屬性,或者比較二者的name屬性,若是一致則匹配成功。
一、解析dtb
在start_kernel() --> setup_arch(0 --> unflatten_device_tree() --> __unflatten_device_tree()函數中掃描dtb,並轉換成節點是device_node的樹狀結構。
注:代碼基於linux4.1.15內核(下同)spa

static void __unflatten_device_tree()
{
    ...
	/* First pass, scan for size */
	start = 0;
	size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
	size = ALIGN(size, 4);
    ...
	/* Second pass, do actual unflattening */
	start = 0;
	unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
   ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2. 註冊dts設備

imx6q_init_machine() --> of_platform_populate()。
在of_platform_populate()中循環掃描根節點下的各節點:

int of_platform_populate()
{
    ...
	for_each_child_of_node(root, child) {
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
     }
     ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
static int of_platform_bus_create()
{
    ...
	/* Make sure it has a compatible property */
	if (strict && (!of_get_property(bus, "compatible", NULL))) {
		pr_debug("%s() - skipping %s, no compatible prop\n",
			 __func__, bus->full_name);
		return 0;
	}

	auxdata = of_dev_lookup(lookup, bus);
	if (auxdata) {
		bus_id = auxdata->name;
		platform_data = auxdata->platform_data;
	}
    ...
	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
	if (!dev || !of_match_node(matches, bus))
		return 0;
    若是節點有子節點,則遞歸調用of_platform_bus_create()掃描節點的子節點:
	for_each_child_of_node(bus, child) {
		pr_debug("   create child: %s\n", child->full_name);
		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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

最終調用of_platform_device_create_pdata() —> of_device_add() 註冊設備並添加到對應的鏈表中。

三、註冊驅動
Linux註冊驅動的函數爲driver_register(),或者其包裝函數如platform_driver_register(),而driver_register()或者其包裝函數通常在驅動的初始化函數xxx_init()中調用。
驅動初始化函數xxx_init()被調用的路勁爲:
start_kernel() --> rest_init() --> Kernel_init() --> kernel_init_freeable() --> do_basic_setup() --> do_initcalls:
在這裏插入圖片描述

簡而言之,在start_kernel()中調用driver_register()註冊驅動程序。

四、匹配設備
追蹤driver_register()函數,driver_register() --> bus_add_driver() --> driver_attach() --> __driver_attach:

static int __driver_attach(struct device *dev, void *data)
{
	struct device_driver *drv = data;
	if (!driver_match_device(drv, dev))
		return 0;

	if (dev->parent)	/* Needed for USB */
		device_lock(dev->parent);
	device_lock(dev);
	if (!dev->driver)
		driver_probe_device(drv, dev);
	device_unlock(dev);
	if (dev->parent)
		device_unlock(dev->parent);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

driver_match_device()中尋找匹配的設備,若是匹配成功則執行驅動的probe函數。
driver_match_device()最終會調用平臺的匹配函數platform_match():

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

從代碼中能夠看出, platform_match()會採用多種方法進行匹配:

  1. of_driver_match_device將根據驅動程序of_match_table中的compatible屬性,與設備中的compatible屬性進行比對。
  2. 其次調用acpi_driver_match_device()進行匹配。
  3. 若是前2種方法都沒有匹配的,最後比對設備和驅動的name字符串是否一致。

以GPIO-key爲例,設備和驅動匹配示意圖以下:
在這裏插入圖片描述

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接: https://blog.csdn.net/ethercat_i7/article/details/83786670
相關文章
相關標籤/搜索