DTS是Device Tree Source的縮寫,用來描述設備的硬件細節。在過去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代碼,相當多數的代碼只是在描述板級細節,而這些板級細節對於內核來講,不過是垃圾,如板上的platform設備、resource、i2c_board_info、spi_board_info以及各種硬件的platform_data。為了去掉這些垃圾代碼,Linux采用DTS這種新的數據結構來描述硬件設備。采用Device Tree后,許多硬件的細節可以直接透過它傳遞給Linux,而不再需要在kernel中進行大量的冗余編碼。
有關DTS的語法格式,網上有很多資料,這里我就不再贅述了。本文主要講Linux是怎樣加載DTS設備節點的流程。下面以高通QCT8974平台(Linux內核)為例進行講解:
(1)使用DTS注冊平台總線的過程
在講注冊過程之前,我們先看看DTS是怎樣描述平台總線結構的,以i2c總線為例:
- / {
- model = "Qualcomm MSM 8974";
- compatible = "qcom,msm8974";
- interrupt-parent = <&intc>;
- aliases {
- spi0 = &spi_0;
- spi7 = &spi_7;
- sdhc1 = &sdhc_1; /* SDC1 eMMC slot */
- sdhc2 = &sdhc_2; /* SDC2 SD card slot */
- sdhc3 = &sdhc_3; /* SDC3 SDIO slot */
- sdhc4 = &sdhc_4; /* SDC4 SDIO slot */
- };
- memory {
- secure_mem: secure_region {
- linux,contiguous-region;
- reg = <0 0x7800000="">;
- label = "secure_mem";
- };
- adsp_mem: adsp_region {
- linux,contiguous-region;
- reg = <0 0x2000000="">;
- label = "adsp_mem";
- };
- };
- intc: interrupt-controller@F9000000 {
- compatible = "qcom,msm-qgic2";
- interrupt-controller;
- #interrupt-cells = <3>;
- reg = <0xf9000000 0x1000="">,
- <0xf9002000 0x1000="">;
- };
- msmgpio: gpio@fd510000 {
- compatible = "qcom,msm-gpio";
- gpio-controller;
- #gpio-cells = <2>;
- interrupt-controller;
- #interrupt-cells = <2>;
- reg = <0xfd510000 0x4000="">;
- ngpio = <146>;
- interrupts = <0 208="" 0="">;
- qcom,direct-connect-irqs = <8>;
- };
- wcd9xxx_intc: wcd9xxx-irq {
- compatible = "qcom,wcd9xxx-irq";
- interrupt-controller;
- #interrupt-cells = <1>;
- interrupt-parent = <&msmgpio>;
- interrupts = <72 0="">;
- interrupt-names = "cdc-int";
- };
- timer {
- compatible = "arm,armv7-timer";
- interrupts = <1 2="" 0="" 1="" 3="" 0="">;
- clock-frequency = <19200000>;
- };
- i2c_0: i2c@f9967000 { /* BLSP#11 */
- cell-index = <0>;
- compatible = "qcom,i2c-qup";
- reg = <0xf9967000 0x1000="">;
- #address-cells = <1>;
- #size-cells = <0>;
- reg-names = "qup_phys_addr";
- interrupts = <0 105="" 0="">;
- interrupt-names = "qup_err_intr";
- qcom,i2c-bus-freq = <100000>;
- qcom,i2c-src-freq = <50000000>;
- };
- i2c_2: i2c@f9924000 {
- cell-index = <2>;
- compatible = "qcom,i2c-qup";
- reg = <0xf9924000 0x1000="">;
- #address-cells = <1>;
- #size-cells = <0>;
- reg-names = "qup_phys_addr";
- interrupts = <0 96="" 0="">;
- interrupt-names = "qup_err_intr";
- qcom,i2c-bus-freq = <100000>;
- qcom,i2c-src-freq = <50000000>;
- };
- spi_0: spi@f9923000 {
- compatible = "qcom,spi-qup-v2";
- reg = <0xf9923000 0x1000="">;
- interrupts = <0 95="" 0="">;
- spi-max-frequency = <19200000>;
- #address-cells = <1>;
- #size-cells = <0>;
- gpios = <&msmgpio 3 0>, /* CLK */
- <&msmgpio 1 0>, /* MISO */
- <&msmgpio 0 0>; /* MOSI */
- cs-gpios = <&msmgpio 9 0>;
- };
- };
從上面可知,系統平台上掛載了很多總線,如i2c、spi、uart等等,每一個總線分別被描述為一個節點。Linux在啟動后,到C入口時,會執行以下操作,加載系統平台上的總線和設備:
start_kernel() --> setup_arch() --> unflatten_device_tree()
unflatten_device_tree()的代碼如下:
- void __init unflatten_device_tree(void)
- {
- __unflatten_device_tree(initial_boot_params, &allnodes,
- early_init_dt_alloc_memory_arch);
- /* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */
- of_alias_scan(early_init_dt_alloc_memory_arch);
- }
在執行完unflatten_device_tree()后,DTS節點信息被解析出來,保存到allnodes鏈表中,allnodes會在后面被用到。
隨后,當系統啟動到board文件時,會調用.init_machine,高通8974平台對應的是msm8974_init()。接着調用of_platform_populate(....)接口,加載平台總線和平台設備。至此,系統平台上的所有已配置的總線和設備將被注冊到系統中。注意:不是dtsi文件中所有的節點都會被注冊,在注冊總線和設備時,會對dts節點的狀態作一個判斷,如果節點里面的status屬性沒有被定義,或者status屬性被定義了並且值被設為“ok”或者“okay”,其他情況則不被注冊到系統中。
(2)使用DTS注冊總線設備的過程
上面講了Linux怎樣使用DTS注冊平台總線和平台設備到系統中,那么其他設備,例如i2c、spi設備是怎樣注冊到系統中的呢?下面我們就以i2c設備為例,講解Linux怎樣注冊i2c設備到系統中。
以高通8974平台為例,在注冊i2c總線時,會調用到qup_i2c_probe()接口,該接口用於申請總線資源和添加i2c適配器。在成功添加i2c適配器后,會調用of_i2c_register_devices()接口。此接口會解析i2c總線節點的子節點(掛載在該總線上的i2c設備節點),獲取i2c設備的地址、中斷號等硬件信息。然后調用request_module()加載設備對應的驅動文件,調用i2c_new_device(),生成i2c設備。此時設備和驅動都已加載,於是drvier里面的probe方法將被調用。后面流程就和之前一樣了。
簡而言之,Linux采用DTS描述設備硬件信息后,省去了大量板文件垃圾信息。Linux在開機啟動階段,會解析DTS文件,保存到全局鏈表allnodes中,在掉用.init_machine時,會跟據allnodes中的信息注冊平台總線和設備。值得注意的是,加載流程並不是按找從樹根到樹葉的方式遞歸注冊,而是只注冊根節點下的第一級子節點,第二級及之后的子節點暫不注冊。Linux系統下的設備大多都是掛載在平台總線下的,因此在平台總線被注冊后,會根據allnodes節點的樹結構,去尋找該總線的子節點,所有的子節點將被作為設備注冊到該總線上。
