在“Step by Step為HPS添加UART外設”章節,我們講解了如何使用SoC EDS軟件為創建好的包含HPS的Qsys系統添加UART外設并生成相應的設備樹(dts)文件。在“基于Linux應用程序的HPS配置FPGA”章節,我們也提到了使用開發軟件安裝包提供的不含FPGA邏輯部分的設備樹文件來配合啟動Linux系統。那么什么是設備樹?如何得到適配硬件系統的設備樹?linux系統又是如何使用設備樹信息來加載各種設備驅動的呢?本節將針對上述問題,以一個具體的實例,講解設備樹的運用。
在講到設備樹之前,先看一個具體的應用場景。對于一個ARM處理器,一般其片上都會集成了有較多的外設接口,包括I2C、SPI、UART等。而I2C、SPI都屬于總線屬性,在這些總線上又會連接其它的外部器件。例如在I2C總線上,又會連接EEPROM、I2C接口的各種傳感器、LCD顯示屏、RTC等。那么Linux系統如何能夠知道I2C總線上連接了哪些設備?又如何知道這些設備的具體信息呢?
在早期的Linux系統中,使用的是硬件描述文件的形式來實現該功能的。每一個具體的硬件平臺都會在Linux系統源碼包的arch/arm/mach-xxx/目錄下存在一個硬件信息描述的源碼包,在該源碼包中定義了GPIO的使用、外設、i2c總線等系統信息。如果對某個硬件平臺進行了修改,例如將EEPROM的容量從16Kb更換為了64Kb,或者在I2C總線上新增了一個從機,則需要修改對應的硬件描述文件,然后重新編譯內核。
在arch/arm/下定義了很多mach-xxx的文件夾,一般是按照廠商或者平臺命名,例如高通平臺的為mach-msm,marvell的為mach-mmp,mach-pxa。
隨著新的硬件平臺不斷產生,為了支持這些硬件平臺,Linux系統中會增加越來越多的板級描述文件,從而導致系統中的冗雜文件越來越多。
為了解決這個問題,Linux內核從3.x開始引入設備樹的概念,用于實現驅動代碼與設備信息相分離。在設備樹出現以前,所有關于設備的具體信息都要寫在驅動里,一旦外圍設備變化,驅動代碼就要重寫。引入了設備樹之后,驅動代碼只負責處理驅動的邏輯,而關于設備的具體信息存放到設備樹文件中,這樣,如果只是硬件接口信息的變化而沒有驅動邏輯的變化,驅動開發者只需要修改設備樹文件信息,不需要改寫驅動代碼。
比如在ARM Linux內,一個.dts(device tree source)文件對應一個ARM的machine,一般放置在內核的"arch/arm/boot/dts/"目錄內,比如友晶的DE0-nano-SoC開發板就是"arch/arm/boot/dts/socfpga_cyclone5_de0_sockit.dts"。這個文件可以通過$make dtbs命令編譯成二進制的.dtb文件供內核驅動使用。
基于同樣的軟件分層設計的思想,由于一個SoC可能對應多個machine,如果每個machine的設備樹都寫成一個完全獨立的.dts文件,那么勢必相當一些.dts文件有重復的部分,為了解決這個問題,Linux設備樹目錄把一個SoC公用的部分或者多個machine共同的部分提煉為相應的.dtsi文件。這樣每個.dts就只有自己差異的部分,公有的部分只需要"include"相應的.dtsi文件, 這樣就使整個設備樹的管理更加有序。例如,對于Intel的SoC FPGA器件,其包括Cyclone V、Arria V、Arria 10三個系列,這三個系列中,有很多內容是相同的,可以作為公共部分,因此在linux源碼中,使用了socfpga.dtsi文件來描述所有socfpga器件通用的部分,然后針對Cyclone V、Arria V、Arria 10這三個系列,又分別使用了socfpga_cyclone5.dtsi、socfpga_arria5.dtsi、socfpga_arria10.dtsi三個文件來描述各個系列的硬件中公共的部分。當具體到某個特定的硬件板卡,如DE0-nano-SoC開發,其設備樹文件socfpga_cyclone5_de0_sockit.dts正文的第一行就是使用了#include"socfpga_cyclone5.dtsi"來包含cyclone5器件的通用部分,而在socfpga_cyclone5.dtsi文件中,正文的第一行又是使用了#include "socfpga.dtsi"來包含所有socfpga器件的通用部分。通過這種方式,簡化了設備樹的構成。
設備樹用樹狀結構描述設備信息,它有以下幾種特性
每個設備樹文件都有一個根節點,每個設備都是一個節點。
節點間可以嵌套,形成父子關系,這樣就可以方便的描述設備間的關系。
每個設備的屬性都用一組key-value對(鍵值對)來描述。
每個屬性的描述用“;”結束
為了方便分析,這里以“Step by Step為HPS添加UART外設”章節中生成的soc_system.dts文件的內容為例,介紹設備樹文件的基本格式。
soc_system.dts文件中開頭部分內容如下所示:
0008 / { 0009 model = "Altera SOCFPGA Cyclone V"; 0010 compatible = "altr,socfpga-cyclone5", "altr,socfpga"; 0011 #address-cells = <1>; 0012 #size-cells = <1>; 0013 height = <2>; /* appended from boardinfo */ 0014 width = <16>; /* appended from boardinfo */ 0015 brightness = <8>; /* appended from boardinfo */ 0016 pagesize = <32>; /* appended from boardinfo */ 0017 0018 aliases { 0019 ethernet0 = "/sopc@0/ethernet@0xff702000"; 0020 }; //end aliases 0021 0022 cpus { 0023 #address-cells = <1>; 0024 #size-cells = <0>; 0025 enable-method = "altr,socfpga-smp"; 0026 0027 hps_0_arm_a9_0: cpu@0x0 { 0028 device_type = "cpu"; 0029 compatible = "arm,cortex-a9-17.1", "arm,cortex-a9"; 0030 reg = <0x00000000>; 0031 next-level-cache = <&hps_0_L2>; 0032 }; //end cpu@0x0 (hps_0_arm_a9_0) 0033 0034 hps_0_arm_a9_1: cpu@0x1 { 0035 device_type = "cpu"; 0036 compatible = "arm,cortex-a9-17.1", "arm,cortex-a9"; 0037 reg = <0x00000001>; 0038 next-level-cache = <&hps_0_L2>; 0039 }; //end cpu@0x1 (hps_0_arm_a9_1) 0040 }; //end cpus 0041 0042 memory { 0043 device_type = "memory"; 0044 reg = <0xffff0000 0x00010000>, 0045 <0x00000000 0x80000000>; 0046 }; //end memory |
第8行,一個“/”表示一個硬件平臺,該硬件平臺有以下屬性
model:產品型號,為AlteraSOCFPGA Cyclone V。
compatible:兼容屬性,用來描述產品與Linux系統中支持的哪個平臺兼容。
height、width、brightness:這些屬性用于描述板上某專用硬件的一些物理信息,例如這里的height為2、width為16,實際上是描述了Inte原廠開發板上提供的LCD顯示屏的顯示高度和寬度,AC501-SoC開發板上并未設置該LCD顯示屏,但是該部分硬件我們依舊保留在了hps_common_board_info.xml文件中,方便讀者參考學習。
第18行~20行,描述了一個基本的以太網節點信息, ethernet@0xff702000表示該以太網位于絕對地址為0xff702000的位置,而根據Cyclone V 器件手冊,0xff702000這個地址正是EMAC1的絕對地址。
第22行~40行,cpus節點,描述了該開發板上的CPU節點信息。在SoC FPGA器件中,包含了兩個Cortex-A9的CPU,因此在cpus節點中又包含了兩個子節點,分別名為hps_0_arm_a9_0和hps_0_arm_a9_1。
再如第88行~195行:
0088 sopc0: sopc@0 { 0089 device_type = "soc"; 0090 ranges; 0091 #address-cells = <1>; 0092 #size-cells = <1>; 0093 compatible = "ALTR,avalon", "simple-bus"; 0094 bus-frequency = <0>; 0095 0096 hps_0_bridges: bridge@0xc0000000 { 0097 compatible = "altr,bridge-17.1", "simple-bus"; 0098 reg = <0xc0000000 0x20000000>, 0099 <0xff200000 0x00200000>; 0100 reg-names = "axi_h2f", "axi_h2f_lw"; 0101 clocks = <&clk_0 &clk_0>; 0102 clock-names = "h2f_axi_clock", "h2f_lw_axi_clock"; 0103 #address-cells = <2>; 0104 #size-cells = <1>; 0105 ranges = <0x00000001 0x00000000 0xff200000 0x00000008>, 0106 <0x00000001 0x00000100 0xff200100 0x00000080>, 0107 <0x00000001 0x00010000 0xff210000 0x00000008>, 0108 <0x00000001 0x00010040 0xff210040 0x00000020>, 0109 <0x00000001 0x000100c0 0xff2100c0 0x00000010>, 0110 <0x00000001 0x00000060 0xff200060 0x00000020>, 0111 <0x00000001 0x00000020 0xff200020 0x00000020>, 0112 <0x00000001 0x00000040 0xff200040 0x00000020>; 0113 0114 i2c_0: unknown@0x100000000 { 0115 compatible = "unknown,unknown-1.0"; 0116 reg = <0x00000001 0x00000000 0x00000008>; 0117 interrupt-parent = <&hps_0_arm_gic_0>; 0118 interrupts = <0 41 4>; 0119 clocks = <&clk_0>; 0120 }; //end unknown@0x100000000 (i2c_0) 0121 0122 alt_vip_vfr_tft: vip@0x100000100 { 0123 compatible = "ALTR,vip-frame-reader-14.0", "ALTR,vip-frame-reader-9.1"; 0124 reg = <0x00000001 0x00000100 0x00000080>; 0125 clocks = <&clk_0>; 0126 max-width = <800>; / 0127 max-height = <480>; 0128 bits-per-color = <8>; /* 0129 colors-per-beat = <4>; /* 0130 beats-per-pixel = <1>; 0131 mem-word-width = <128>; 0132 }; //end vip@0x100000100 (alt_vip_vfr_tft) 0133 0134 sysid_qsys: sysid@0x100010000 { 0135 compatible = "altr,sysid-17.1", "altr,sysid-1.0"; 0136 reg = <0x00000001 0x00010000 0x00000008>; 0137 clocks = <&clk_0>; 0138 id = <2899645186>; 0139 timestamp = <1532912636>; /* 0140 }; //end sysid@0x100010000 (sysid_qsys) 0141 0142 led_pio: gpio@0x100010040 { 0143 compatible = "altr,pio-17.1", "altr,pio-1.0"; 0144 reg = <0x00000001 0x00010040 0x00000020>; 0145 clocks = <&clk_0>; 0146 altr,gpio-bank-width = <2>; /* 0147 resetvalue = <0>; /* 0148 #gpio-cells = <2>; 0149 gpio-controller; 0150 }; //end gpio@0x100010040 (led_pio) 0151 0152 button_pio: gpio@0x1000100c0 { 0153 compatible = "altr,pio-17.1", "altr,pio-1.0"; 0154 reg = <0x00000001 0x000100c0 0x00000010>; 0155 interrupt-parent = <&hps_0_arm_gic_0>; 0156 interrupts = <0 43 1>; 0157 clocks = <&clk_0>; 0158 altr,gpio-bank-width = <2>; /* 0159 altr,interrupt-type = <2>; /* 0160 altr,interrupt_type = <2>; /* 0161 edge_type = <1>; /* 0162 level_trigger = <0>; /* 0163 resetvalue = <0>; /* 0164 #gpio-cells = <2>; 0165 gpio-controller; 0166 }; //end gpio@0x1000100c0 (button_pio) 0167 0168 uart_0: serial@0x100000060 { 0169 compatible = "altr,uart-17.1", "altr,uart-1.0"; 0170 reg = <0x00000001 0x00000060 0x00000020>; 0171 interrupt-parent = <&hps_0_arm_gic_0>; 0172 interrupts = <0 44 4>; 0173 clocks = <&clk_0>; 0174 clock-frequency = <50000000>; /* 0175 current-speed = <115200>; /* 0176 }; //end serial@0x100000060 (uart_0) 0177 0178 uart_1: serial@0x100000020 { 0179 compatible = "altr,uart-17.1", "altr,uart-1.0"; 0180 reg = <0x00000001 0x00000020 0x00000020>; 0181 interrupt-parent = <&hps_0_arm_gic_0>; 0182 interrupts = <0 42 4>; 0183 clocks = <&clk_0>; 0184 clock-frequency = <50000000>; /* 0185 current-speed = <115200>; /* 0186 }; //end serial@0x100000020 (uart_1) 0187 0188 spi_0: spi@0x100000040 { 0189 compatible = "altr,spi-17.1", "altr,spi-1.0"; 0190 reg = <0x00000001 0x00000040 0x00000020>; 0191 interrupt-parent = <&hps_0_arm_gic_0>; 0192 interrupts = <0 40 4>; 0193 clocks = <&clk_0>; 0194 }; //end spi@0x100000040 (spi_0) 0195 }; //end bridge@0xc0000000 (hps_0_bridges) |
該部分首先是在第88行描述了一個名為sopc的節點,而在該節點下,又包含了一個名為hps_0_bridges的子節點,該節點表示了"axi_h2f"和 "axi_h2f_lw"兩個HPS到FPGA的通信橋。在該通信橋節點上,又描述了I2C控制器(i2c_0)、FrameReader控制器(alt_vip_vfr_tft)、設備ID(sysid_qsys)、基于PIO的LED控制器(led_pio)、基于PIO的按鍵控制器(button_pio)、串口控制器(uart_0、uart_1)、spi控制器(spi_0)。這些節點所代表的設備正是我們在Platform Designer中添加的FPGA側的IP。因此,如果我們在FPGA側增加、刪除、修改了某些IP,然后使用SoC EDS軟件重新生成dts文件,這些變化也都會體現在hps_0_bridges節點下。例如我們修改添加的uart_1控制器的默認波特率為9600bps,然后重新生成dts文件,則可以看到dts文件中uart_1節點下的current-speed屬性值會從115200變為9600。用戶也可以對比AC501_SoC_GHRD工程生成的dts文件,是沒有uart_1這個節點的,只有在經過了“Step by Step為HPS添加UART外設”實驗后得到的新工程生成的dts文件,才有uart_1節點。
了解了什么是設備樹,那么,設備樹又是怎么指導linux內核加載指定驅動的呢,或者說,Linux的驅動程序是怎么樣確定系統中有這個硬件,以及該硬件的具體參數,并完成驅動的加載和設置的呢?具體內容,可以關注小梅哥“9 Linux設備樹的原理與應用實例”系列文章的下一篇文章“Linux設備樹應用實例”。
新書預告
由小梅哥編寫,Intel FPGA大學計劃經理推薦,駿龍科技大力支持的基于Intel SoC FPGA 器件的設計和開發教程已經上線預售,京東天貓均已上架,歡迎小伙伴圍觀。