Linux和設(shè)備樹https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/devicetree/usage-model.txt?h=v5.2-rc5
設(shè)備樹數(shù)據(jù)的Linux使用模型
作者:Grant Likely grant.likely@secretlab.ca
本文介紹Linux如何使用設(shè)備樹。概述
可以在設(shè)備樹使用情況頁面上找到設(shè)備樹數(shù)據(jù)格式
在https://github.com/devicetree-org/devicetree-specification
“開放固件設(shè)備樹(Open Firmware Device Tree)”或簡稱為設(shè)備樹(DT)是數(shù)據(jù)
描述硬件的結(jié)構(gòu)和語言。更具體地說,是對操作系統(tǒng)可讀的硬件的描述。這樣,操作系統(tǒng)就無需對應(yīng)用程序的詳細(xì)信息進(jìn)行硬編碼。
從結(jié)構(gòu)上講,DT是具有命名節(jié)點的樹或無環(huán)圖,并且節(jié)點可以封裝任意數(shù)量的命名屬性任意數(shù)據(jù)。還存在一種機制來創(chuàng)建任意從一個節(jié)點到自然樹結(jié)構(gòu)外部的另一個節(jié)點的鏈接。
從概念上講,一組通用的使用約定(稱為“綁定”)定義了數(shù)據(jù)應(yīng)如何在樹中出現(xiàn)以描述典型
硬件特性,包括數(shù)據(jù)總線,中斷線,GPIO,連接和外圍設(shè)備。
盡可能使用現(xiàn)有的綁定來描述硬件最大限度地利用現(xiàn)有支持代碼,但是由于屬性和節(jié)點名稱只是文本字符串,很容易擴展現(xiàn)有的綁定或通過定義新的節(jié)點和屬性來創(chuàng)建新的節(jié)點。警惕,
但是,無需先做功課就可以創(chuàng)建新的綁定關(guān)于已經(jīng)存在的東西。目前有兩種不同不兼容,因為新的i2c總線的綁定是在沒有先調(diào)查i2c設(shè)備如何進(jìn)行的情況下創(chuàng)建的,在現(xiàn)有系統(tǒng)中已經(jīng)被枚舉。
1.歷史
DT最初是由Open Firmware創(chuàng)建的,它是用于將數(shù)據(jù)從開放固件傳遞到客戶端的通信方法程序(類似于操作系統(tǒng))。一個操作系統(tǒng)使用了設(shè)備樹,用于在運行時發(fā)現(xiàn)硬件的拓?fù)?,以?br> 從而支持大多數(shù)可用硬件,而無需硬編碼信息(假設(shè)驅(qū)動程序適用于所有設(shè)備)。
由于開放式固件通常在PowerPC和SPARC平臺上使用,Linux對這些架構(gòu)的支持已經(jīng)使用設(shè)備樹很長時間了。
2005年,當(dāng)PowerPC Linux開始進(jìn)行重大清理并合并32位和64位支持,因此決定要求所有
powerpc平臺,無論是否使用開放固件(Open Firmware)。為此,DT表示稱為Flattened Device
樹(FDT)已創(chuàng)建,可以作為二進(jìn)制文件傳遞到內(nèi)核blob不需要真正的開放固件實現(xiàn)。 U-Boot,
kexec和其他引導(dǎo)程序進(jìn)行了修改,以支持通過設(shè)備樹二進(jìn)制(dtb)并在引導(dǎo)時修改dtb。 DT原為
還添加到PowerPC引導(dǎo)包裝程序(arch / powerpc / boot / *)中,以便dtb可以與內(nèi)核映像包裝在一起以支持引導(dǎo)現(xiàn)有的非DT感知固件。
一段時間后,F(xiàn)DT基礎(chǔ)架構(gòu)被普遍使用所有架構(gòu)。在撰寫本文時,有6條主線架構(gòu)(arm,microblaze,mips,powerpc,sparc和x86)和1主線之外(nios)具有一定程度的DT支持。
2.數(shù)據(jù)模型
如果您尚未閱讀設(shè)備樹使用情況[1]頁面,
然后立即閱讀。沒關(guān)系,我等...
2.1高級視圖
要了解的最重要的一點是DT只是數(shù)據(jù)描述硬件的結(jié)構(gòu)。它沒有什么神奇的,并且不會神奇地使所有硬件配置出現(xiàn)的問題消除。它所做的是提供一種將主板中的硬件配置以及設(shè)備驅(qū)動程序中的支持
Linux內(nèi)核(或與此相關(guān)的任何其他操作系統(tǒng))。使用它允許電路板和設(shè)備支持成為數(shù)據(jù)驅(qū)動;使
根據(jù)傳遞到內(nèi)核中的數(shù)據(jù)而不是基于每臺機器的硬編碼選擇。
理想情況下,數(shù)據(jù)驅(qū)動的平臺設(shè)置應(yīng)減少代碼數(shù)量復(fù)制并使其更容易支持各種硬件帶有單個內(nèi)核映像。
Linux將DT數(shù)據(jù)用于三個主要目的:
1)平臺識別;
2)運行時配置,以及
3)設(shè)備數(shù)量。
2.2平臺識別
首先,內(nèi)核將使用DT中的數(shù)據(jù)來識別特定機器。在理想世界中,特定平臺不應(yīng)該對內(nèi)核至關(guān)重要,因為將描述所有平臺細(xì)節(jié)通過設(shè)備樹以一致和可靠的方式完美實現(xiàn)。盡管硬件不是完美的,但是內(nèi)核必須識別機器在早期啟動期間運行,以便有機會運行機器特定的修復(fù)程序。
在大多數(shù)情況下,機器標(biāo)識是無關(guān)緊要的,并且內(nèi)核將根據(jù)計算機的內(nèi)核選擇設(shè)置代碼
CPU或SoC。例如,在ARM上,位于arch/arm/kernel/setup.c的setup_arch()將在以下位置調(diào)用setup_machine_fdt()在machine_desc中搜索的arch / arm / kernel / devtree.c
表并選擇與設(shè)備樹最匹配的machine_desc數(shù)據(jù)。它通過查看“兼容”來確定最佳匹配根設(shè)備樹節(jié)點中的屬性,并將其與struct machine_desc中的dt_compat列表(在arch/arm/include/asm/mach/arch.h(如果您感到好奇)。
'compatible'屬性包含以字符串開頭的排序列表并帶有機器的確切名稱,后跟一個可選列表兼容的主板,從最兼容到最不兼容。對于例如TI BeagleBoard及其根的根兼容屬性
后繼者,BeagleBoard xM板可能分別如下所示:
compatible = "ti,omap3-beagleboard", "ti,omap3450", "ti,omap3";
compatible = "ti,omap3-beagleboard-xm", "ti,omap3450", "ti,omap3";
如果“ ti,omap3-beagleboard-xm”指定了確切的模型,它也會聲稱它與OMAP 3450 SoC和omap3系列兼容一般的SoC。您會注意到列表是從大多數(shù)排序特定(精確的板)到最小特定(SoC系列)。
精明的讀者可能會指出Beagle xM也可能聲稱與原始的Beagle板兼容。但是,應(yīng)該請注意,在董事會一級這樣做通常是因為從一個板到另一個板的高水平變更,即使在同一板內(nèi)產(chǎn)品線,很難準(zhǔn)確地確定當(dāng)一個板聲稱與另一個兼容。對于頂層,它是最好謹(jǐn)慎一點,不要聲稱一個板是與另一個兼容。值得注意的例外是主板是另一個的載體,例如連接到主板上的CPU模塊載板。
關(guān)于兼容值的另一注。兼容中使用的任何字符串屬性必須記錄其所指示的內(nèi)容。加文檔/設(shè)備樹/綁定中的兼容字符串的文檔。
再次在ARM上,對于每個machine_desc,內(nèi)核都會查看是否任何dt_compat列表條目都會出現(xiàn)在compatible屬性中。如果是,則該machine_desc是驅(qū)動機。搜索完整個machine_descs表之后,
setup_machine_fdt()返回基于“最兼容”的machine_desc每個machine_desc在兼容屬性中的哪個條目上匹配反對。如果找不到匹配的machine_desc,則返回NULL。
該方案背后的原因是觀察到在某些情況下,單個machine_desc可以支持大量板
如果它們都使用相同的SoC或相同的SoC系列。然而,總是會有一些例外,具體的板將
需要特殊的設(shè)置代碼,這些代碼在一般情況下沒有用。特殊情況可以通過顯式檢查是否存在
通用設(shè)置代碼中麻煩的板子,但是很快如果不僅僅是幾個,就會變得丑陋和/或難以維護(hù)。
相反,兼容列表允許通用的machine_desc提供通過指定“較少”來支持廣泛的通用板dt_compat列表中的“ compatible”值。在上面的示例中通用板支持可以要求與“ ti,omap3”兼容,或者“ ti,omap3450”。如果在原始Beagleboard上發(fā)現(xiàn)錯誤在早期啟動期間需要特殊的變通方法代碼,然后重新啟動可以添加machine_desc來實現(xiàn)解決方法,并且僅在“ ti,omap3-beagleboard”上匹配。
PowerPC使用略有不同的方案,在其中調(diào)用.probe()鉤子從每個machine_desc,并使用第一個返回TRUE的。但是,這種方法沒有考慮到兼容列表,對于新架構(gòu),應(yīng)該避免使用支持。
2.3運行時配置
在大多數(shù)情況下,DT是從以下設(shè)備傳遞數(shù)據(jù)的唯一方法固件到內(nèi)核,因此也習(xí)慣于傳遞運行時和
配置數(shù)據(jù),例如內(nèi)核參數(shù)字符串和位置initrd鏡像。
大部分?jǐn)?shù)據(jù)包含在/ chosen節(jié)點中以及引導(dǎo)時
Linux看起來像這樣:
chosen {
bootargs = "console=ttyS0,115200 loglevel=8";
initrd-start = <0xc8000000>;
initrd-end = <0xc8200000>;
};
bootargs屬性包含內(nèi)核參數(shù)和initrd- *
屬性定義initrd Blob的地址和大小。注意
initrd-end是initrd映像之后的第一個地址,因此這不是
匹配結(jié)構(gòu)資源的通常語義。所選節(jié)點也可能
(可選)包含任意數(shù)量的其他屬性,用于
平臺特定的配置數(shù)據(jù)。
在早期啟動期間,體系結(jié)構(gòu)設(shè)置代碼調(diào)用of_scan_flat_dt()
多次使用不同的幫助程序回調(diào)來解析設(shè)備樹
設(shè)置分頁之前的數(shù)據(jù)。 of_scan_flat_dt()代碼掃描通過
設(shè)備樹并使用助手來提取所需的信息
在早期啟動期間。通常,early_init_dt_scan_chosen()幫助器
用于解析所選節(jié)點,包括內(nèi)核參數(shù),
early_init_dt_scan_root()初始化DT地址空間模型,
和early_init_dt_scan_memory()確定大小和
可用RAM的位置。
在ARM上,功能setup_machine_fdt()負(fù)責(zé)早期
選擇正確的machine_desc后掃描設(shè)備樹
支持董事會。
2.4設(shè)備數(shù)量
確定板卡后以及早期配置數(shù)據(jù)之后已被解析,則內(nèi)核初始化可以正常進(jìn)行辦法。在此過程中的某個時刻,將調(diào)用unflatten_device_tree()將數(shù)據(jù)轉(zhuǎn)換為更有效的運行時表示形式。
這也是機器特定的安裝掛鉤將被調(diào)用的時候,例如machine_desc .init_early()、. init_irq()和.init_machine()掛鉤在ARM上。本節(jié)的其余部分使用ARM的示例實施,但是所有架構(gòu)都將執(zhí)行幾乎相同的操作使用DT時的事情。
可以通過名稱猜出,.init_early()用于任何機器-需要在啟動過程中盡早執(zhí)行的特定設(shè)置,和.init_irq()用于設(shè)置中斷處理。使用DT不會從本質(zhì)上改變這兩個函數(shù)的行為。
如果提供了DT,則.init_early()和.init_irq()都可以調(diào)用任何DT查詢函數(shù)(include / linux / of * .h中的of_ *)獲取有關(guān)平臺的其他數(shù)據(jù)。
DT上下文中最有趣的鉤子是.init_machine(),它主要負(fù)責(zé)使用以下命令填充Linux設(shè)備模型
有關(guān)平臺的數(shù)據(jù)。從歷史上講,嵌入式平臺通過定義一組靜態(tài)時鐘結(jié)構(gòu),platform_devices以及板上的其他數(shù)據(jù)支持.c文件,以及在.init_machine()中進(jìn)行注冊。如果使用DT,則而不是為每個平臺硬編碼靜態(tài)設(shè)備,可以通過解析DT并分配設(shè)備來獲得設(shè)備動態(tài)結(jié)構(gòu)。
最簡單的情況是.init_machine()僅負(fù)責(zé)注冊一個platform_devices塊。 platform_device是一個概念由Linux用于無法檢測的內(nèi)存或I / O映射設(shè)備通過硬件,以及用于“復(fù)合”或“虛擬”設(shè)備(更多關(guān)于這些設(shè)備)后來)。盡管DT沒有“平臺設(shè)備”術(shù)語,平臺設(shè)備大致對應(yīng)于根目錄中的設(shè)備節(jié)點樹和簡單內(nèi)存映射的總線節(jié)點的子代。
大約現(xiàn)在是布置示例的好時機。這是NVIDIA Tegra主板的設(shè)備樹。
/{
compatible = "nvidia,harmony", "nvidia,tegra20";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
chosen { };
aliases { };
memory {
device_type = "memory";
reg = <0x00000000 0x40000000>;
};
soc {
compatible = "nvidia,tegra20-soc", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
intc: interrupt-controller@50041000 {
compatible = "nvidia,tegra20-gic";
interrupt-controller;
#interrupt-cells = <1>;
reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >;
};
serial@70006300 {
compatible = "nvidia,tegra20-uart";
reg = <0x70006300 0x100>;
interrupts = <122>;
};
i2s1: i2s@70002800 {
compatible = "nvidia,tegra20-i2s";
reg = <0x70002800 0x100>;
interrupts = <77>;
codec = <&wm8903>;
};
i2c@7000c000 {
compatible = "nvidia,tegra20-i2c";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x7000c000 0x100>;
interrupts = <70>;
wm8903: codec@1a {
compatible = "wlf,wm8903";
reg = <0x1a>;
interrupts = <347>;
};
};
};
sound {
compatible = "nvidia,harmony-sound";
i2s-controller = <&i2s1>;
i2s-codec = <&wm8903>;
};
};
在.init_machine()時,需要查看Tegra板支持代碼
此DT并確定要為其創(chuàng)建platform_devices的節(jié)點。
但是,看著那棵樹,并不能立即看出是哪種樹
每個節(jié)點代表的設(shè)備數(shù)量,或者即使一個節(jié)點代表一個設(shè)備
完全沒有/ chosen,/ aliases和/ memory節(jié)點是信息性的
沒有描述設(shè)備的節(jié)點(盡管可以說內(nèi)存可能是
被視為設(shè)備)。 / soc節(jié)點的子代是內(nèi)存映射的
設(shè)備,但codec @ 1a是i2c設(shè)備,聲音節(jié)點
代表的不是設(shè)備,而是其他設(shè)備的連接方式
一起創(chuàng)建音頻子系統(tǒng)。我知道每個設(shè)備是什么
因為我熟悉電路板設(shè)計,但是內(nèi)核如何
知道如何處理每個節(jié)點?
訣竅是內(nèi)核從樹的根部開始并看起來
具有“兼容”屬性的節(jié)點。首先,通常
假定具有“兼容”屬性的任何節(jié)點都代表一個設(shè)備
其次,可以假定根的任何節(jié)點
的樹要么直接連接到處理器總線,要么是
不能用其他任何方式描述的其他系統(tǒng)設(shè)備。
對于這些節(jié)點中的每一個,Linux都會分配并注冊一個
platform_device,后者又可能綁定到platform_driver。
為什么對這些節(jié)點使用platform_device是一個安全的假設(shè)?
好吧,對于Linux建模設(shè)備的方式,幾乎所有的bus_types
假定其設(shè)備是總線控制器的子級。對于
例如,每個i2c_client是i2c_master的子級。每個spi_device
是SPI總線的子代。同樣適用于USB,PCI,MDIO等。
在DT中也找到了相同的層次結(jié)構(gòu),其中僅I2C設(shè)備節(jié)點
曾經(jīng)作為I2C總線節(jié)點的子代出現(xiàn)。 SPI,MDIO,USB的同上
等等。唯一不需要特定父類型的設(shè)備
設(shè)備是platform_devices(和amba_devices,但更多有關(guān)
稍后),它將很高興地存在于Linux / sys / devices的基礎(chǔ)上
樹。因此,如果DT節(jié)點位于樹的根目錄,則它
實際上,最好將其注冊為platform_device。
Linux板支持代碼調(diào)用of_platform_populate(NULL,NULL,NULL,NULL)
在樹的根部開始發(fā)現(xiàn)設(shè)備。的
參數(shù)全為NULL,因為從
樹,無需提供起始節(jié)點(第一個NULL),
父結(jié)構(gòu)設(shè)備(最后一個NULL),并且我們沒有使用匹配項
表(尚未)。對于只需要注冊設(shè)備的板,
.init_machine()可以完全為空,除了
of_platform_populate()調(diào)用。
在Tegra示例中,這說明了/ soc和/ sound節(jié)點,但是
SoC節(jié)點的子代呢?他們不應(yīng)該注冊嗎
作為平臺設(shè)備嗎?對于Linux DT支持,一般行為
用于由父級的設(shè)備驅(qū)動程序在以下位置注冊子級設(shè)備
驅(qū)動程序.probe()時間。因此,i2c總線設(shè)備驅(qū)動程序?qū)⒆砸粋€
每個子節(jié)點都有i2c_client,SPI總線驅(qū)動程序?qū)⒆?br>
它的spi_device子級,以及類似的其他bus_type。
根據(jù)該模型,可以編寫綁定到
SoC節(jié)點,只需為其每個節(jié)點注冊platform_devices
孩子們。電路板支持代碼將分配和注冊SoC
設(shè)備,(理論上的)SoC設(shè)備驅(qū)動程序可以綁定到SoC設(shè)備,
并為/ soc / interrupt-controller,/ soc / serial注冊platform_devices,
/ soc / i2s和/ soc / i2c在其.probe()掛鉤中。容易吧?
實際上,事實證明,注冊一些
platform_devices和更多platform_devices是一種常見模式,
設(shè)備樹支持代碼反映了這一點,并給出了上面的示例
更簡單。 of_platform_populate()的第二個參數(shù)是
of_device_id表,以及與該表中的條目匹配的任何節(jié)點
還將注冊其子節(jié)點。在Tegra情況下,代碼
可以看起來像這樣:
static void __init harmony_init_machine(void)
{
/* ... */
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
}
在設(shè)備樹規(guī)范中將“簡單總線”定義為一個屬性
意味著一個簡單的內(nèi)存映射總線,因此of_platform_populate()代碼
可以寫成只是假設(shè)與簡單總線兼容的節(jié)點
總是被遍歷。但是,我們將其作為參數(shù)傳遞給
主板支持代碼始終可以覆蓋默認(rèn)行為。
[需要增加有關(guān)添加i2c / spi / etc子設(shè)備的討論]
附錄A:AMBA設(shè)備
ARM Primecell是連接到ARM AMBA的某種設(shè)備
總線,其中包括對硬件檢測和電源的一些支持
管理。在Linux中,struct amba_device和amba_bus_type為
用于表示Primecell設(shè)備。但是,奇怪的是
并非AMBA總線上的所有設(shè)備都是Primecell,而對于Linux,它是
amba_device和platform_device實例的典型情況是
同一總線段的兄弟姐妹。
使用DT時,這會導(dǎo)致of_platform_populate()出現(xiàn)問題
因為它必須決定是否將每個節(jié)點注冊為一個
platform_device或amba_device。不幸的是,這使
設(shè)備創(chuàng)建模型,但是該解決方案卻沒有
太有侵略性了。如果節(jié)點與“ arm,amba-primecell”兼容,則
of_platform_populate()會將其注冊為amba_device而不是
platform_device。