1.設備樹
以上一節為例子,一個leddrv.c驅動可以對應board_A.c、board_B.c、board_C.c等一系列的單板。一旦修改了代碼,就要重新編譯加載。而且隨着板卡的增多,這么.c文件也會越來越多。
因此,linux就開始引入設備樹。設備樹其實是一個配置文件,里面定義了硬件相關的資源。這樣就取代了這些board_A.c、board_B.c、board_C.c這些用來描述硬件資源的文件。
2.設備樹在開發板上的體現
ls /sys/firmware
devicetree目錄下是以目錄結構呈現dtb文件,根節點對應base目錄,每一個節點對應一個目錄,每一個屬性對應一個文件。
這些屬性的值如果是字符串,可以使用 cat 命令把它打印出來;對於數值,可以用hexdump 把它打印出來。
uboot會在啟動內核時把設備樹在內存中的地址告訴內核。
3.設備樹語法
DTS 文件布局:
/dts-v1/; // 表示版本 [memory reservations] // 格式為: /memreserve/ <address> <length>; / { [property definitions] [child nodes] };
node 的格式:
設備樹中的基本單元,被稱為“node”,其格式為:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
如果該node沒有reg屬性,那么該節點名字中必須不能包括@和unit-address。
unit-address的具體格式是和設備掛在哪個bus上相關。例如對於cpu,其unit-address就是從0開始編址,以此加一。而具體的設備,例如以太網控制器,其unit-address就是寄存器地址。@unit-address通常用區分名字相同的外設備。
按照慣例,如果一個node有一個reg屬性,那么這個node-name必須包括unit-address,這是reg屬性的第一個address值。
label 是標號,可以省略。label 的作用是為了方便地引用 node,比如:
/dts-v1/; / { uart0: uart@fe001000 { compatible="ns16550"; reg=<0xfe001000 0x100>; }; };
可以使用下面 2 種方法來修改 uart@fe001000 這個 node:
// 在根節點之外使用 label 引用 node: &uart0 { status = “disabled”; }; 或在根節點之外使用全路徑: &{/uart@fe001000} { status = “disabled”; };
cpu addressing
在討論尋址時,CPU節點代表了最簡單的情況。 每個CPU都分配有一個唯一的ID,並且沒有與CPU ID相關聯的大小。
cpus { #address-cells = <1>; #size-cells = <0>; cpu@0 { compatible = "arm,cortex-a9"; reg = <0>; }; cpu@1 { compatible = "arm,cortex-a9"; reg = <1>; }; };
在cpus節點,#address-cells被設置成了1,#size-cells被設置成了0。這是說子reg值是單獨的uint32,它用無大小字段表示地址。在此情況下,這兩個cpu分配到的地址為0和1。Cpu節點的#size-cells是0因為每個cpu只分配到了一個單獨的地址。
仍然需要注意reg值需要與節點名的值相匹配。按照慣例,如果一個節點有一個reg屬性,那么這個節點名稱必須包括unit-address,這是reg屬性的第一個address值。
memory mapped devices
與在cpu節點中單獨的address值不同,內存映射設備被分配了一系列將要響應的地址,因此不僅需要包含內存的基地址而且還需要映射地址的長度,因此需要使用#size-cells用來表示在每個子reg元組中長度字段的大小。在以下示例中,每個address值為1 cell(32 bits),每個長度值也是1 cell,這在32 bit系統是比較典型的。64 bit設備也許會為#address-cells和#size-cells使用數值2,在device tree中獲取64 bit addressing。
/dts-v1/; / { #address-cells = <1>; #size-cells = <1>; ... serial@101f0000 { compatible = "arm,pl011"; reg = <0x101f0000 0x1000 >; }; serial@101f2000 { compatible = "arm,pl011"; reg = <0x101f2000 0x1000 >; }; gpio@101f3000 { compatible = "arm,pl061"; reg = <0x101f3000 0x1000 0x101f4000 0x0010>; }; interrupt-controller@10140000 { compatible = "arm,pl190"; reg = <0x10140000 0x1000 >; }; spi@10115000 { compatible = "arm,pl022"; reg = <0x10115000 0x1000 >; }; ... };
non memory mapped devices
處理器總線的其它設備為非內存映射設備。他們有地址范圍,但不能被CPU直接尋址。母設備的驅動程序將代替CPU進行間接訪問。以i2c設備為例,每個設備都分配了一個地址,但沒有長度或范圍與之相匹配。這與CPU地址分配很相似。
i2c@1,0 { compatible = "acme,a1234-i2c-bus"; #address-cells = <1>; #size-cells = <0>; reg = <1 0 0x1000>; rtc@58 { compatible = "maxim,ds1338"; reg = <58>; }; };
ranges(address translations)
我們已經討論過如何向設備分配地址,但此時這些地址只是本地設備節點,還沒有說明如何從那些地址里映射到cpu可以使用的地址。根節點經常描述地址空間的CPU視圖。根節點的子節點已經使用了CPU的address domain,所以不需要任何明確的映射。例如,serial@101f0000設備被直接分配了地址0x101f0000。
根節點的非直接子節點是無法使用CPU的address domain的。為了在deivce tree獲取內存映射地址必須指定如何從一個域名將地址轉換到另一個。Ranges屬性就用於此目的。以下是添加了ranges屬性的device tree示例。
/dts-v1/; / { compatible = "acme,coyotes-revenge"; #address-cells = <1>; #size-cells = <1>; ... external-bus { #address-cells = <2> #size-cells = <1>; ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet 1 0 0x10160000 0x10000 // Chipselect 2, i2c controller 2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash ethernet@0,0 { compatible = "smc,smc91c111"; reg = <0 0 0x1000>; }; i2c@1,0 { compatible = "acme,a1234-i2c-bus"; #address-cells = <1>; #size-cells = <0>; reg = <1 0 0x1000>; rtc@58 { compatible = "maxim,ds1338"; reg = <58>; }; }; flash@2,0 { compatible = "samsung,k8f1315ebm", "cfi-flash"; reg = <2 0 0x4000000>; }; }; };
Ranges是一個地址轉換列表。每個輸入ranges表格的是包含子地址的元組,母地址和子地址空間的范圍大小。每個字段的大小都由獲取的子地址的#address-cells值,母地址的#address-cell值和子地址的#size-cells值而定。以外部總線為例,子地址是2 cells,母地址是1 cell,大小也為1 cell。轉換三個ranges:
- Offset 0 from chip select 0 is mapped to address range 0x10100000…0x1010ffff
- Offset 0 from chip select 1 is mapped to address range 0x10160000…0x1016ffff
- Offset 0 from chip select 2 is mapped to address range 0x30000000…0x30ffffff
例如上面的總線是有片選的,就需要描述片選及片選的偏移量,在說明地址時,還需要說明地址映射范圍。
4.中斷映射
與遵循樹的自然結構而進行的地址轉換不同,機器上的任何設備都可以發起和終止中斷信號。另外地址的編址也不同於中斷信號,前者是設備樹的自然表示,而后者者表現為獨立於設備樹結構的節點之間的鏈接。 下圖顯示了設備的自然結構以及每個節點在邏輯中斷樹中的位置。
上圖包括以下部分:
- open-pic中斷控制器是中斷樹的根
- 中斷樹根有三個子設備,它們將中斷直接路由到open-pic
- device1
- PCI host controller
- GPIO Controller
- 存在三個中斷域; 一個以開放式pic節點為根,一個在PCI主橋節點,一個在GPIO Controller節點上
- 有兩個nexus節點; 一個位於PCI主橋,一個位於GPIO控制器。
下面顯示了具有PCI總線控制器和采樣中斷的設備片段
soc { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; open-pic { clock-frequency = <0>; interrupt-controller; #address-cells = <0>; #interrupt-cells = <2>; }; pci { #interrupt-cells = <1>; #size-cells = <2>; #address-cells = <3>; interrupt-map-mask = <0xf800 0 0 7>; interrupt-map = < / * IDSEL 0x11 - PCI slot 1* / 0x8800 0 0 1 &open-pic 2 1 / * INTA* / 0x8800 0 0 2 &open-pic 3 1 / * INTB* / 0x8800 0 0 3 &open-pic 4 1 / * INTC* / 0x8800 0 0 4 &open-pic 1 1 / * INTD* / / * IDSEL 0x12 - PCI slot 2* / 0x9000 0 0 1 &open-pic 3 1 / * INTA* / 0x9000 0 0 2 &open-pic 4 1 / * INTB* / 0x9000 0 0 3 &open-pic 1 1 / * INTC* / 0x9000 0 0 4 &open-pic 2 1 / * INTD* / >; }; };
5.dtsi文件
設備樹文件不需要我們從零寫出來,內核支持了某款芯片比如 imx6ull,在內核的arch/arm/boot/dts目錄下就有了能用的設備樹模板,一般命名為xxxx.dtsi。“i”表示“include”,被別的文件引用的。
我們使用某款芯片制作出了自己的單板,所用資源跟 xxxx.dtsi 是大部分相同,小部分不同,所以需要引腳 xxxx.dtsi 並修改。
dtsi 文件跟 dts 文件的語法是完全一樣的。
dts 中可以包含.h 頭文件,也可以包含 dtsi 文件,在.h 頭文件中可以定義一些宏。
/dts-v1/; #include <dt-bindings/input/input.h> #include "imx6ull.dtsi" / { …… };
6.常用的屬性
#address-cells、#size-cells
cell 指一個 32 位的數值:
address-cells:address 要用多少個 32 位數來表示;
size-cells:size 要用多少個 32 位數來表示大小。
比如一段內存,怎么描述它的起始地址和大小?
下例中,address-cells 為 1,所以 reg 中用 1 個數來表示地址,即用 0x80000000 來表示地址;size-cells 為 1,所以 reg 中用 1 個數來表示大小,即用 0x20000000 表示大小:
/ { #address-cells = <1>; #size-cells = <1>; memory { reg = <0x80000000 0x20000000>; }; };
compatible
“compatible”表示“兼容”,對於某個 LED,內核中可能有 A、B、C 三個驅動都支持它,那可以這樣寫:
led { compatible = “A”, “B”, “C”; };
內核啟動時,就會為這個 LED 按這樣的優先順序為它找到驅動程序:A、B、C。
根節點下也有 compatible 屬性,用來選擇哪一個“machine desc”:一個內核可以支持machine A,也支持 machine B,內核啟動后會根據根節點的 compatible 屬性找到對應的machine desc 結構體,執行其中的初始化函數。
compatible 的值,建議取這樣的形式:"manufacturer,model",即“廠家名,模塊名”。
model
model 屬性與 compatible 屬性有些類似,但是有差別。
compatible 屬性是一個字符串列表,表示可以你的硬件兼容 A、B、C 等驅動;
model 用來准確地定義這個硬件是什么。
比如根節點中可以這樣寫:
/ { compatible = "samsung,smdk2440", "samsung,mini2440"; model = "jz2440_v3"; };
它表示這個單板,可以兼容內核中的“smdk2440”,也兼容“mini2440”。
從 compatible 屬性中可以知道它兼容哪些板,但是它到底是什么板?用 model 屬性來明確。
status
dtsi 文件中定義了很多設備,但是在你的板子上某些設備是沒有的。這時你可以給這個設備節點添加一個 status 屬性,設置為“disabled”:
&uart1 { status = "disabled"; };
reg
reg 的本意是 register,用來表示寄存器地址。
但是在設備樹里,它可以用來描述一段空間。反正對於 ARM 系統,寄存器和內存是統一編址的,即訪問寄存器時用某塊地址,訪問內存時用某塊地址,在訪問方法上沒有區別。
reg 屬性的值,是一系列的“address size”,用多少個 32 位的數來表示 address 和 size,由其父節點的#address-cells、#size-cells 決定。
name(過時了,建議不用)
它的值是字符串,用來表示節點的名字。在跟 platform_driver 匹配時,優先級最低。
compatible 屬性在匹配過程中,優先級最高。
device_type(過時了,建議不用)
它的值是字符串,用來表示節點的類型。在跟 platform_driver 匹配時,優先級為中。
compatible 屬性在匹配過程中,優先級最高。
memory@30000000 { device_type = "memory"; reg = <0x30000000 0x20000000>; };
device_type屬性定義了該node的設備類型,例如cpu、serial等。對於memory node,其device_type必須等於memory。
根節點
dts 文件中必須有一個根節點:
/dts-v1/; / { model = "SMDK24440"; compatible = "samsung,smdk2440"; #address-cells = <1>; #size-cells = <1>; };
根節點中必須有這些屬性:
#address-cells // 在它的子節點的 reg 屬性中, 使用多少個 u32 整數來描述地址(address) #size-cells // 在它的子節點的 reg 屬性中, 使用多少個 u32 整數來描述大小(size) compatible // 定義一系列的字符串, 用來指定內核中哪個machine_desc可以支持本設備 // 即這個板子兼容哪些平台 // uImage : smdk2410 smdk2440 mini2440 ==> machine_desc model // 咱這個板子是什么 // 比如有 2 款板子配置基本一致, 它們的 compatible 是一樣的 // 那么就通過 model 來分辨這 2 款板子
CPU 節點
一般不需要我們設置,在 dtsi 文件中都定義好了:
cpus { #address-cells = <1>; #size-cells = <0>; cpu0: cpu@0 { ....... } };
多核的通常會設置cpu的頻率。
cpus { #address-cells = <1>; #size-cells = <0>; cpu0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <0x0>; clock-frequency = <1600000000>; }; cpu1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <0x1>; clock-frequency = <1600000000>; }; cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <0x2>; clock-frequency = <1600000000>; }; cpu3: cpu@3 { device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <0x3>; clock-frequency = <1600000000>; }; };
memory 節點
芯片廠家不可能事先確定你的板子使用多大的內存,所以 memory 節點需要板廠設置,比如:
memory { reg = <0x80000000 0x20000000>; };
chosen 節點
我們可以通過設備樹文件給內核傳入一些參數,這要在 chosen 節點中設置 bootargs 屬性
chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; };
6.編譯、更換設備樹
6.1在內核中直接make
設置 ARCH、CROSS_COMPILE、PATH 這三個環境變量后,進入 ubuntu 上板子內核源碼的目錄,執行如下命令即可編譯 dtb 文件:
make dtbs V=1
編譯成功會在arch/arm/boot/dts/目錄下有相關單板的dtb文件。
然后在板子啟動后相對應位置替換一下dtb文件就可以了。
單獨編譯:
make dtbs CROSS_COMPILE=arm-none-linux-gnueabi-
反編譯:
dtc -I dtb -O dts -o tmp.dts s5pv210-x210.dtb
https://blog.csdn.net/u012489236?t=1
https://blog.csdn.net/qq_16777851/article/details/88958098