Linux和設(shè)備樹(Device Tree)

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。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容