1.設(shè)備樹概念
1.1.設(shè)備樹感性認(rèn)識(shí)
設(shè)備樹(Device Tree),將這個(gè)詞分開就是“設(shè)備”和“樹”,描述設(shè)備樹的文件叫做DTS(Device Tree Source),這個(gè)DTS 文件采用樹形結(jié)構(gòu)描述板級(jí)設(shè)備,比如CPU 數(shù)量、 內(nèi)存基地址、IIC 接口上接了哪些設(shè)備、SPI 接口上接了哪些設(shè)備等等。設(shè)備樹是樹形數(shù)據(jù)結(jié)構(gòu),具有描述系統(tǒng)中設(shè)備的節(jié)點(diǎn)。每個(gè)節(jié)點(diǎn)都有描述所代表設(shè)備特征的鍵值對(duì)。每個(gè)節(jié)點(diǎn)只有一個(gè)父節(jié)點(diǎn),而根節(jié)點(diǎn)則沒(méi)有父節(jié)點(diǎn)。
1.2.DTS、DTB、DTC
DTS:設(shè)備樹源碼文件;DTB:將DTS編譯后得到的二進(jìn)制文件;DTC:DTS的編譯工具,其源碼在內(nèi)核的scriptsdtc目錄下。基于同樣arm架構(gòu)的CPU有很多,同一個(gè)CPU會(huì)制作很多配置不一的板子,如何正確的編譯所選的板子的DTS文件呢?在內(nèi)核的arch/arm/boot/dts/Makefile中:
?
dtb-$(CONFIG_ARCH_XXX)?+=?xxx.dtb dtb-$(CONFIG_ARCH_XXX)?+=?xxx-sip.dtb dtb-$(CONFIG_ARCH_XXX)?+=?xxx.dtb dtb-$(CONFIG_ARCH_XXX)?+=?xxx.dtb
?
例如xxxx的開發(fā)板,只要設(shè)置CONFIG_ARCH_xxx=y,所有用到這顆SOC的DTS都會(huì)編譯成DTB。如果后續(xù)還用到了這顆SOC設(shè)計(jì)的開發(fā)板,只要新建一個(gè)DTS文件,并將對(duì)應(yīng)名稱的DTB文件名加到dtb-$(CONFIG_ARCH_xxx)中,在編譯設(shè)備樹時(shí)就會(huì)將DTS編譯為二進(jìn)制的DTB文件。
1.3.Device Tree語(yǔ)法
以下語(yǔ)法分析均以xxx.dts為例。
1.3.1.dtsi頭文件
設(shè)備樹的頭文件擴(kuò)展名為 .dtsi。以xxx.dts為例,其包含以下頭文件。
?
#include?"skeleton.dtsi" #include?xxx.h" #include?"xxx-clocks.dtsi" #include?"xxx-pinctrl.dtsi" #include?"xxx-camera.dtsi"
?
需要注意的是.dts文件不但可以引用.dtsi文件,還可以引用.h文件和其他的.dts文件。Q1:每一個(gè).dtsi和.dts都有自己的根節(jié)點(diǎn),但是一個(gè)設(shè)備樹文件只允許有一個(gè)根節(jié)點(diǎn),DTC如何處理?將根節(jié)點(diǎn)合并,保留最后一級(jí)的根節(jié)點(diǎn)。包含的頭文件內(nèi)容會(huì)被展開,展開的位置在/memory和/cpus之間。(存疑,只用xxx.dts編譯過(guò))Q2:如果包含過(guò)程中有重復(fù)的compatible,DTC怎么處理?編譯時(shí)不會(huì)報(bào)錯(cuò),會(huì)生成兩個(gè)compatible屬性一樣的節(jié)點(diǎn)。
1.3.2.設(shè)備節(jié)點(diǎn)
設(shè)備樹中的每一個(gè)節(jié)點(diǎn)都按照以下格式命名:
?
node-name@unit-address
?
node-name表示節(jié)點(diǎn)名稱,它的長(zhǎng)度范圍應(yīng)該是1~31個(gè)字符,可以由以下的字符組成:
字符 | 說(shuō)明 |
---|---|
0~9 | 數(shù)字 |
a-z | 小寫字母 |
A-Z | 大寫字母 |
, | 逗號(hào)(英文) |
. | 句號(hào)(英文) |
_ | 下劃線(英文) |
+ | 加號(hào) |
- | 減號(hào) |
表 2-1節(jié)點(diǎn)名稱的有效字符
節(jié)點(diǎn)名稱應(yīng)以較低或大寫字符開頭,并應(yīng)描述設(shè)備的一般類別。節(jié)點(diǎn)的單位地址特定于節(jié)點(diǎn)所在的總線類型。它由表2-1中字符集中的一個(gè)或多個(gè)ASCII字符組成。單位地址必須與節(jié)點(diǎn)的reg屬性中指定的第一個(gè)地址匹配。如果節(jié)點(diǎn)沒(méi)有reg屬性,則必須省略@unit-address,并且單獨(dú)使用節(jié)點(diǎn)名稱將節(jié)點(diǎn)與樹中相同級(jí)別的其他節(jié)點(diǎn)區(qū)分開來(lái)。對(duì)于reg格式和單位地址,特定總線的綁定可能會(huì)指定附加更具體的要求。根節(jié)點(diǎn)沒(méi)有節(jié)點(diǎn)名稱或單位地址。它由正斜杠(/)標(biāo)識(shí)。
圖 2-1節(jié)點(diǎn)名稱示例
在圖2-1中,節(jié)點(diǎn)名稱為cpu的兩個(gè)節(jié)點(diǎn)通過(guò)uint-address 0和1區(qū)分;節(jié)點(diǎn)名稱為ethernet的兩個(gè)節(jié)點(diǎn)通過(guò)uint-address fe002000和fe003000區(qū)分。在設(shè)備樹中經(jīng)常會(huì)看到以下設(shè)備名稱:
?
watchdog:?watchdog@04009800?
?
冒號(hào)前的是節(jié)點(diǎn)標(biāo)簽(label),冒號(hào)后是節(jié)點(diǎn)名稱。引入label的目的是方便訪問(wèn)節(jié)點(diǎn),可以直接通過(guò)&label來(lái)訪問(wèn)這個(gè)節(jié)點(diǎn)。比如上述節(jié)點(diǎn)就可以使用&watchdog來(lái)訪問(wèn)。
1.3.2.1.通用名稱建議
節(jié)點(diǎn)的名稱應(yīng)該有些通用,反映設(shè)備的功能,而不是其精確的編程模型。如適用,名稱應(yīng)為以下選擇之一:
?
??adc??????accelerometer ??atm??????audio-codec ??audio-controller???backlight: ??bluetooth?????bus ??cache-controller???camera ??can??????charger ??clock:?????clock-controller ??compact-flash????cpu ??cpus??????crypto ??disk??????display ??dma-controller???dsp ??eeprom?????efuse: ??mdio??????memory ??memory-controller???mmc ??mmc-slot?????mouse ??nand-controller???nvram ??oscillator????parallel ??pc-card?????pci ??pcie??????phy ??pinctrl?????pmic ??pmu??????port ??ports??????pwm
?
1.3.2.2.路徑名稱
通過(guò)指定從根節(jié)點(diǎn)到所需節(jié)點(diǎn)的完整路徑(通過(guò)所有子節(jié)點(diǎn)),可以唯一識(shí)別devicetree中的節(jié)點(diǎn)。指定設(shè)備路徑的約定是:
?
?/node-name-1/node-name-2/.../node-name-N
?
例如,在圖2-1中,到cpu#1的設(shè)備路徑為:
?
/cpus/cpu@1
?
/為根節(jié)點(diǎn),在保證完整路徑明確的前提下,可以省略u(píng)int-address。
1.3.3.屬性
設(shè)備樹中的每個(gè)節(jié)點(diǎn)都有描述節(jié)點(diǎn)特性的屬性。屬性由名稱和值組成。
1.3.3.1.屬性名稱
屬性名稱的長(zhǎng)度范圍應(yīng)該是1~31個(gè)字符,可以由以下的字符組成:
字符 | 說(shuō)明 |
---|---|
0~9 | 數(shù)字 |
a-z | 小寫字母 |
A-Z | 大寫字母 |
, | 逗號(hào)(英文) |
. | 句號(hào)(英文) |
_ | 下劃線(英文) |
+ | 加號(hào) |
? | 問(wèn)號(hào)(英文) |
# | #號(hào)(hash) |
非標(biāo)準(zhǔn)屬性名稱應(yīng)指定唯一的字符串前綴,例如股票代號(hào),用于標(biāo)識(shí)定義該屬性的公司或組織的名稱。示例:
?
xxx,pin-function?=?<6>; fsl,channel-fifo-len ??linux,network-index ??ibm,ppc-interrupt-server#s
?
1.3.3.2.屬性值
屬性值是包含與屬性關(guān)聯(lián)的信息的零或多個(gè)字節(jié)的數(shù)組。
數(shù)值 | 描述 |
---|---|
、 | 值為空,用于描述bool信息。個(gè)人理解類似于flag參數(shù) |
、 | 32位整數(shù),采用big-endian格式。示例:32位值0x11223344將在內(nèi)存中表示為:地址 ? ?11 ?地址 + 122 ?地址 + 233 ? 地址 + 344 |
、 | 表示采用big-endian格式的64位整數(shù)。包括兩個(gè)值,其中第一個(gè)值包含整數(shù)的最有效位,第二個(gè)值包含最小有效位。例如:64位值0x1122334455667788將表示為兩個(gè)單元格:<0x11223344 0x55667788>。 |
、 | 格式特定于屬性,參見屬性定義。 |
、 | 字符串可打印且以空值(?)結(jié)尾。 |
、 | 一個(gè)值。phandle值是引用設(shè)備樹中另一個(gè)節(jié)點(diǎn)的方法。可被引用的任何節(jié)點(diǎn)都用唯一的定義了phandle屬性數(shù)值。該數(shù)字用于帶有phandle值類型的屬性值。 |
、 | 串聯(lián)在一起的值列表。 |
big-endian和little-endian(大小端):big-endian:是指低地址端存放高位字節(jié);little-endian:是指低地址端存放低位字節(jié);
?
1.3.3.3.標(biāo)準(zhǔn)屬性
Compatible(兼容)
屬性名稱 | 兼容值 |
---|---|
值類型 |
|
描述 | 兼容屬性值由定義設(shè)備特定編程模型的一個(gè)或多個(gè)字符串組成。客戶端程序應(yīng)使用此字符串列表選擇設(shè)備驅(qū)動(dòng)程序。該屬性值包含一個(gè)從最特定到最通用的null終止字符串的串聯(lián)列表。它們?cè)试S設(shè)備表達(dá)其與一系列類似設(shè)備的兼容性,可能允許單個(gè)設(shè)備驅(qū)動(dòng)器與幾個(gè)設(shè)備匹配。推薦的格式是“制造商,型號(hào)”,其中制造商是描述制造商名稱的字符串(如股票代號(hào))。 |
示例:
compatible?=“fsl,mpc8641”,“ns16550”;
?
在此示例中,操作系統(tǒng)將首先嘗試查找支持fsl,mpc8641-uartmpc8641的設(shè)備驅(qū)動(dòng)程序。如果找不到驅(qū)動(dòng)程序,然后,它將嘗試定位受支持的更通用的ns16550設(shè)備類型驅(qū)動(dòng)程序 。
一般驅(qū)動(dòng)程序文件都會(huì)有個(gè)OF匹配表,此匹配表保存著一些compatible值,如果設(shè)備節(jié)點(diǎn)的 compatible屬性值和OF匹配表中的任何一個(gè)值相等,那么就表示設(shè)備可以使用這驅(qū)動(dòng)。比如在文件drvier/misc/memctrl.c中:
?
static?struct?of_device_id_xxx_memctrl_of_match[]?=?{ ?????{?.compatible?=?"xxxx,memctrl",?}, ?????{}, };
?
對(duì)應(yīng)的,在arch/arm/boot/dts/xxx.dts中有:
?
memctrl:?memctrl?{ ???compatible?=?"xxxx,memctrl"; ???reg?=?<0x0121B000?0x1044>; ???clocks?=?<&sdram_bandw_clk>,?<&mem_axi_clk>; ???clock-names?=?"sdram_bandwidth_clk",?"mem_axi_clk"; ???interrupts?=?; ???interrupt-controller; ???#interrupt-cells?=?<1>; };
?
Model(型號(hào))
屬性名稱 | 模型值 |
---|---|
值類型 | ? |
描述 | 指定設(shè)備的制造商型號(hào)。推薦的格式為:“制造商,型號(hào)”,其中制造商是描述制造商名稱的字符串(如股票代號(hào))。 |
示例:
?
model =“fsl,MPC8349EMITX”;
?
Phandle(pointer handle)
屬性名稱 | pointer handle值 |
---|---|
值類型 | ? |
描述 | phandle屬性指定設(shè)備樹中唯一節(jié)點(diǎn)的數(shù)字標(biāo)識(shí)符。phandle屬性值被需要引用與該屬性關(guān)聯(lián)的節(jié)點(diǎn)的其他節(jié)點(diǎn)使用。 |
示例:
?
pic@10000000?{ phandle?=?<1>; interrupt-controller; };
?
定義了1的phandle值。另一個(gè)設(shè)備節(jié)點(diǎn)可以引用phandle值為1的pic節(jié)點(diǎn):
?
another-device-node?{ interrupt-parent?=?<1>; };
?
Status
屬性名稱 | 狀態(tài)值 |
---|---|
值類型 |
|
描述 | 狀態(tài)屬性指示設(shè)備的操作狀態(tài),其有效值如下:"okay":指示設(shè)備可運(yùn)行。"disabled":表明設(shè)備目前尚未運(yùn)行,但未來(lái)可能會(huì)運(yùn)行(例如,未插入或關(guān)閉某物)。"fail":表示設(shè)備無(wú)法運(yùn)行,在設(shè)備中檢測(cè)到嚴(yán)重錯(cuò)誤,如果不進(jìn)行維修就不太可能運(yùn)行"fail-sss":表示設(shè)備無(wú)法運(yùn)行,在設(shè)備中檢測(cè)到嚴(yán)重錯(cuò)誤,如果不進(jìn)行維修就不太可能運(yùn)行,sss部分特定于設(shè)備,并指示檢測(cè)到的錯(cuò)誤情況。 |
#address-cells and #size-cells
屬性名稱 | #address-cells,#size-cells |
---|---|
值類型 |
|
描述 | #address-cells和#size-cells屬性可用于設(shè)備樹層次結(jié)構(gòu)中包含子節(jié)點(diǎn)并描述如何解決子設(shè)備節(jié)點(diǎn)的任何設(shè)備節(jié)點(diǎn)。#address-cells屬性定義用于編碼子節(jié)點(diǎn)的reg屬性中地址字段的個(gè)單元格的數(shù)量。#size-cells屬性定義用于編碼子節(jié)點(diǎn)的reg屬性中大小字段的個(gè)單元格的數(shù)量。 |
?
#address-cells?=?<1>; ??#size-cells?=?<0>;
?
表示reg屬性中有一個(gè)u32表示address,沒(méi)有表示reg大小的數(shù)據(jù),所以:reg = <0x0>; 即reg的起始地址為0x0,不描述其大小
?
#address-cells?=?<1>; ???#size-cells?=?<1>;
?
表示reg屬性中有一個(gè)u32表示address,有一個(gè)u32表示size,所以:reg = <0x00000000 0x00040000>; 即reg的起始地址為0x00000000,大小是0x00040000
Reg
屬性名稱 | reg |
---|---|
值類型 |
|
描述 | reg屬性描述設(shè)備資源在其父總線定義的地址空間內(nèi)的地址。這通常意味著內(nèi)存映射IO寄存器塊的偏移和長(zhǎng)度,但在某些總線類型上可能有不同的含義。根節(jié)點(diǎn)定義的地址空間中的地址為cpu真實(shí)地址。該值是一個(gè),由任意數(shù)量的地址和長(zhǎng)度對(duì)組成,<地址長(zhǎng)度>。指定地址和長(zhǎng)度所需的單元格的數(shù)量是總線特定的,由設(shè)備節(jié)點(diǎn)父級(jí)中的#address-cells和#size-cells屬性指定。如果父節(jié)點(diǎn)為#size-cells單元格指定值0,則應(yīng)忽略reg值中的長(zhǎng)度字段。 |
示例:假設(shè)系統(tǒng)芯片中的設(shè)備包含兩個(gè)寄存器塊,SOC中偏移0x3000的32字節(jié)塊和偏移0xFE00的256字節(jié)塊。reg屬性的編碼如下(假設(shè)#address-cells和#size-cells值為1):
?
reg=<0x3000?0x20?0xFE00?0x100>;
?
virtual-reg
屬性名稱 | virtual-reg |
---|---|
值類型 |
|
描述 | virtual-reg屬性指定一個(gè)有效地址,該地址映射到設(shè)備節(jié)點(diǎn)的reg屬性中指定的第一個(gè)物理地址。此屬性使引導(dǎo)程序能夠?yàn)榭蛻舳顺绦蛱峁┮言O(shè)置的虛擬到物理映射。 |
Ranges
屬性名稱 | range |
---|---|
值類型 |
|
描述 | range屬性提供了一種在總線地址空間(子地址空間)和總線節(jié)點(diǎn)父地址空間(父地址空間)之間定義映射或轉(zhuǎn)換的方法。range屬性值的格式是任意數(shù)量的三聯(lián)體(子總線地址、父總線地址、長(zhǎng)度):1.子總線地址是子總線地址空間內(nèi)的物理地址。表示地址的單元格數(shù)取決于總線,可以通過(guò)此節(jié)點(diǎn)(出現(xiàn)range屬性的節(jié)點(diǎn))的#address-cells確定。2. 父總線地址是父總線地址空間中的物理地址。表示父地址的單元格數(shù)取決于總線,可以通過(guò)定義父地址空間的節(jié)點(diǎn)的#address-cells屬性確定。3. 長(zhǎng)度指定子地址空間中范圍的大小。表示大小的單元格數(shù)可以根據(jù)該節(jié)點(diǎn)(出現(xiàn)range屬性的節(jié)點(diǎn))的#size-cells確定。如果屬性用值定義,則它指定父地址和子地址空間相同,并且不需要地址轉(zhuǎn)換。如果總線節(jié)點(diǎn)中不存在該屬性,則假設(shè)節(jié)點(diǎn)的子節(jié)點(diǎn)和父地址空間之間不存在映射。 |
示例:
?
soc?{ ?compatible?=?"simple-bus"; ?#address-cells?=?<1>; ?#size-cells?=?<1>; ?ranges?=?<0x0?0xe0000000?0x00100000>; ?serial?{ ??device_type?=?"serial"; ??compatible?=?"ns16550"; ??reg?=?<0x4600?0x100>; ??clock-frequency?=?<0>; ??interrupts?=?<0xA?0x8>; ??interrupt-parent?=?<&ipic>; ?}; };
?
soc節(jié)點(diǎn)指定了<0x0 0xe0000000 0x00100000>;此屬性值指定對(duì)于1024KB范圍的地址空間,在物理0x0處尋址的子節(jié)點(diǎn)映射到物理0xe0000000的父地址。通過(guò)這種映射,串行設(shè)備節(jié)點(diǎn)可以通過(guò)0xe0004600地址的加載或存儲(chǔ)、0x4600(在注冊(cè)表中指定)的偏移量以及范圍中指定的0xe0000000映射尋址。
dma-ranges
屬性名稱 | dma-ranges |
---|---|
值類型 |
|
描述 | dma-range屬性用于描述存儲(chǔ)器映射總線的直接存儲(chǔ)器訪問(wèn)(dma)結(jié)構(gòu),其設(shè)備樹父級(jí)可以從總線的dma操作訪問(wèn)。它提供了一種定義總線物理地址空間與總線父級(jí)物理地址空間之間映射或轉(zhuǎn)換的方法。dma-range屬性的值的格式是任意數(shù)量的(子總線地址、母線地址、長(zhǎng)度)。指定的每個(gè)三聯(lián)體描述連續(xù)DMA地址范圍。1. 子總線地址是子總線地址空間內(nèi)的物理地址。表示地址的單元格數(shù)取決于總線,可以通過(guò)該節(jié)點(diǎn)(dma-range屬性出現(xiàn)的節(jié)點(diǎn))的#address-cells地址單元格確定。2. 父總線地址是父總線地址空間中的物理地址。表示父地址的單元格數(shù)取決于總線,可以通過(guò)定義父地址空間的節(jié)點(diǎn)的#address-cells屬性確定。3. 長(zhǎng)度指定子地址空間中范圍的大小。表示大小的單元格數(shù)量可以根據(jù)該節(jié)點(diǎn)(dma-range屬性出現(xiàn)的節(jié)點(diǎn))的#size-cells確定。 |
Name(已棄用)
屬性名稱 | name |
---|---|
值類型 |
|
描述 | name屬性用于記錄節(jié)點(diǎn)名字,name屬性已經(jīng)被棄用 ,不推薦使用 name屬性,一些老的設(shè)備樹文件可能會(huì)使用此屬性。 |
device_type
屬性名稱 | device_type |
---|---|
值類型 |
|
描述 | 由于DTS沒(méi)有FCode,因此不建議使用該屬性。只能用于在cpu節(jié)點(diǎn)和memory節(jié)點(diǎn)中,以便與IEEE 1275衍生設(shè)備兼容。 |
1.3.4.基本設(shè)備節(jié)點(diǎn)類型
所有設(shè)備樹文件均要包含一個(gè)根文件,并且所有設(shè)備樹文件均應(yīng)在根節(jié)點(diǎn)下存在以下節(jié)點(diǎn):
1個(gè)/cpus節(jié)點(diǎn)
至少一個(gè)/memory節(jié)點(diǎn)
使用說(shuō)明:R = 必需,O = 可選,OR = 可選但推薦,SD = 參見定義,所有其他的標(biāo)準(zhǔn)屬性均可接受,但可選
1.3.4.1.Root node
devicetree有一個(gè)單獨(dú)的根節(jié)點(diǎn),所有其他設(shè)備節(jié)點(diǎn)都是它的后代。根節(jié)點(diǎn)的完整路徑為/。
屬性名稱 | 使用說(shuō)明 | 類型 | 定義 |
---|---|---|---|
#address-cells | R |
|
root子節(jié)點(diǎn)的reg property地址格式。 |
#size-cells | R |
|
root子節(jié)點(diǎn)的reg property大小格式。 |
model | R |
|
指定唯一標(biāo)識(shí)。系統(tǒng)板型號(hào)。推薦格式為“制造商,型號(hào)” |
compatible | R |
|
指定平臺(tái)體系結(jié)構(gòu)列表。該平臺(tái)兼容。這一建議可供操作系統(tǒng)在選擇平臺(tái)特定代碼時(shí)使用。 |
1.3.4.2./aliases節(jié)點(diǎn)
設(shè)備樹文件可能具有一個(gè)別名節(jié)點(diǎn)(/aliases),該節(jié)點(diǎn)定義一個(gè)或多個(gè)別名屬性。別名節(jié)點(diǎn)應(yīng)位于設(shè)備樹的根節(jié)點(diǎn),并且具有節(jié)點(diǎn)名稱/別名。/aliases節(jié)點(diǎn)的每個(gè)屬性都定義了一個(gè)別名。屬性名稱指定別名。屬性值指定設(shè)備樹中節(jié)點(diǎn)的完整路徑。例如,屬性serial0 = "/simple-bus@fe000000/serial@llc500"定義了別名serial0。別名的命名規(guī)則如下:
字符 | 描述 |
---|---|
0-9 | 數(shù)字 |
a-z | 小寫字母 |
- | 破折號(hào) |
1.3.4.3./memory節(jié)點(diǎn)
所有設(shè)備樹都需要內(nèi)存設(shè)備節(jié)點(diǎn),并描述系統(tǒng)的物理內(nèi)存布局。如果系統(tǒng)具有多個(gè)范圍的內(nèi)存,則可以創(chuàng)建多個(gè)內(nèi)存節(jié)點(diǎn),或者可以在單個(gè)內(nèi)存節(jié)點(diǎn)的reg屬性中指定范圍。
/memory節(jié)點(diǎn)的屬性要求如下:
屬性名稱 | 使用說(shuō)明 | 類型 | 定義 |
---|---|---|---|
device_type | R | O |
|
reg | R |
|
由任意數(shù)量的地址和大小對(duì)組成,它們指定內(nèi)存范圍的物理地址和大小 |
initial-mapped-area | O |
|
指定”初始映射區(qū)域”的地址和大小,是一個(gè)由三元組組成的屬性編碼數(shù)組(有效地址、物理地址、大小)。有效和物理地址均應(yīng)為64位(值),大小應(yīng)為32位(值) |
在xxx.dts中
?
memory?{ ???????reg?=??<0x40000000?0x10000000>;???起始地址0x40000000?長(zhǎng)度0x10000000(32MB) ?};
?
1.3.4.4./chosen 節(jié)點(diǎn)
/chosen 節(jié)點(diǎn)不代表系統(tǒng)中的實(shí)際設(shè)備,而是描述了在運(yùn)行時(shí)由系統(tǒng)固件選擇或指定的參數(shù)。它應(yīng)該是根節(jié)點(diǎn)的子節(jié)點(diǎn)。
屬性名稱 | 使用說(shuō)明 | 類型 | 定義 |
---|---|---|---|
bootargs | O |
|
指定程序的啟動(dòng)參數(shù)。如果不需要引導(dǎo)參數(shù),則該值可能為空字符串 |
stdout-path | O |
|
指定到表示用于引導(dǎo)控制臺(tái)輸出的設(shè)備的節(jié)點(diǎn)的完整路徑。如果包含’:’,則它將終止路徑。該值可以是別名。 |
stdin-path | O |
|
指定到表示用于引導(dǎo)控制臺(tái)輸入的設(shè)備的節(jié)點(diǎn)的完整路徑。如果包含’:’,則它將終止路徑。該值可以是別名。 |
示例:
?
chosen?{ bootargs?=?"root=/dev/nfs?rw?nfsroot=192.168.1.1?console=ttyS0,115200"; };
?
1.3.4.5./cpus節(jié)點(diǎn)屬性
所有設(shè)備樹均需要/cpus/cpu節(jié)點(diǎn)。它并不代表系統(tǒng)中的真實(shí)設(shè)備,而是作為代表系統(tǒng)cpu的子cpu節(jié)點(diǎn)的容器。
屬性名稱 | 使用說(shuō)明 | 類型 | 定義 |
---|---|---|---|
device_type | R |
|
值應(yīng)為“cpu” |
reg | R |
|
它為CPU節(jié)點(diǎn)表示的CPU/線程定義了唯一的CPU/線程ID。如果CPU支持多線程,則reg是一個(gè)數(shù)組,每個(gè)線程具有一個(gè)元素。 |
clock-frequency | R |
|
以Hz為單位指定CPU的當(dāng)前時(shí)鐘速度,格式可以是,或 |
timebase-frequency | R |
|
指定更新時(shí)基的當(dāng)前頻率 |
status | SD |
|
此屬性應(yīng)存在于對(duì)稱多進(jìn)程(SMP)CPU的節(jié)點(diǎn)中 配置。”okay”:CPU正在運(yùn)行;”disable”:CPU處于靜止?fàn)顟B(tài)。 |
1.3.5.中斷映射
在設(shè)備樹中,存在邏輯中斷樹,該邏輯中斷樹表示平臺(tái)硬件中斷的層次結(jié)構(gòu)和路由。在設(shè)備樹中,使用interrupt-parent屬性表示中斷源與中斷控制器的物理連線。代表產(chǎn)生中斷的設(shè)備節(jié)點(diǎn)包含一個(gè)中斷父屬性,該屬性具有一個(gè)虛擬值,指向給設(shè)備的中斷所路由到的設(shè)備(通常是中斷控制器)。
如果產(chǎn)生中斷的設(shè)備不具有中斷父屬性,則假定其中斷父節(jié)點(diǎn)為其設(shè)備父節(jié)點(diǎn)。每個(gè)中斷產(chǎn)生設(shè)備都包含一個(gè)中斷屬性,該屬性的值描述該設(shè)備的一個(gè)或多個(gè)中斷源。每個(gè)源都用稱為中斷描述符表示。中斷描述符的格式和含義是特定于中斷域的,即,取決于中斷域根節(jié)點(diǎn)上節(jié)點(diǎn)的屬性。中斷域的根使用#interrupt-cells屬性定義對(duì)中斷描述符進(jìn)行編碼所需的值數(shù)量。
中斷域是解釋中斷描述符的上下文。中斷域的根可以是中斷控制器(interrupt controller)或中斷連接器(interrupt nexus):
中斷控制器是物理設(shè)備,需要一個(gè)驅(qū)動(dòng)程序來(lái)處理通過(guò)它路由的中斷。它還可能級(jí)聯(lián)到另一個(gè)中斷域。中斷控制器由設(shè)備樹中該節(jié)點(diǎn)上的interrupt-controller指定。
中斷連接器定義了一個(gè)中斷域和另一個(gè)中斷域之間的轉(zhuǎn)換。翻譯基于特定領(lǐng)域和總線的信息。使用interrupt-map屬性在域之間進(jìn)行轉(zhuǎn)換。例如,PCI控制器設(shè)備節(jié)點(diǎn)可以是一個(gè)中斷連接器,定義從PCI中斷命名空間(INTA、INTB等)到具有中斷請(qǐng)求(IRQ)編號(hào)的中斷控制器的轉(zhuǎn)換。
?
1.3.5.1.Interrupts
屬性名稱 | interrupts |
---|---|
值類型 |
|
描述 | 設(shè)備節(jié)點(diǎn)的中斷屬性定義設(shè)備生成的中斷。interrupts屬性的值由任意數(shù)量的中斷描述符組成。中斷描述符的格式由中斷域根定義。 |
示例:
?
interrupts?=?;
?
1.3.5.2.interrupt-parent
屬性名稱 | interrupt-parent |
---|---|
值類型 |
|
描述 | 由于中斷樹中節(jié)點(diǎn)的層次結(jié)構(gòu)可能與device tree不匹配,因此interrupt-parent屬性可用于明確中斷父級(jí)的定義。該值是中斷父級(jí)的phandle。如果設(shè)備缺少此屬性,則假定其中斷父級(jí)為其設(shè)備樹父級(jí)。 |
示例:
?
interrupt-parent?=?<&gpe>;
?
1.3.5.3.interrupts-extended
屬性名稱 | interrupts-extended |
---|---|
值類型 |
|
描述 | 擴(kuò)展的中斷屬性列出了設(shè)備產(chǎn)生的中斷。當(dāng)設(shè)備連接到多個(gè)中斷控制器時(shí),應(yīng)該使用interrupts-extended代替interrupts,因?yàn)樗鼤?huì)在每一個(gè)中斷描述符編碼一個(gè)父代phandle |
示例:
?
interrupts-extended?=?<&pic?0xA?8>,?<&gic?0xda>;
?
1.3.5.4.#interrupt-cells
屬性名稱 | #interrupt-cells |
---|---|
值類型 |
|
描述 | #interrupt-cells屬性定義對(duì)中斷域的中斷描述符進(jìn)行編碼所需的單元數(shù)量 |
1.3.5.5.interrupt-controller
屬性名稱 | interrupt-controller |
---|---|
值類型 |
|
描述 | 中斷控制器屬性的存在將節(jié)點(diǎn)定義為中斷控制器節(jié)點(diǎn)。 |
1.4.Device Tree binary格式
Devicetree Blob (DTB)格式是Devicetree數(shù)據(jù)的平面二進(jìn)制編碼。它用來(lái)在軟件程序之間交換設(shè)備數(shù)據(jù)。例如,在引導(dǎo)操作系統(tǒng)時(shí),固件將向操作系統(tǒng)內(nèi)核傳遞一個(gè)DTB。
DTB格式將devicetree數(shù)據(jù)編碼為一個(gè)單一的、線性的、無(wú)指針的數(shù)據(jù)結(jié)構(gòu)。它由一個(gè)小標(biāo)題組成,接下來(lái)是三個(gè)大小可變的部分:內(nèi)存保留塊、結(jié)構(gòu)塊和字符串塊這些應(yīng)該按照這個(gè)順序出現(xiàn)在扁平的devicetree中。
因此。當(dāng)按地址加載到內(nèi)存中時(shí),設(shè)備樹結(jié)構(gòu)作為一個(gè)整體。將類似于圖中的圖表。
1.4.1.dt_header
設(shè)備樹的頭部是由以下C結(jié)構(gòu)體定義的。所有字段都是32位整數(shù),以big-endian格式存儲(chǔ)。
?
struct?fdt_header?{ 此字段應(yīng)包含值0xd00dfeed(big-endian) ?uint32_t?magic;????/*?magic?word?FDT_MAGIC?*/ 此字段應(yīng)包含設(shè)備數(shù)據(jù)結(jié)構(gòu)的總大小(字節(jié))。該大小應(yīng)包含結(jié)構(gòu)的所有部分:報(bào)頭、內(nèi)存預(yù)留塊、結(jié)構(gòu)塊和字符串塊,以及塊之間或最終塊之后的自由空間間隙。 ?uint32_t?totalsize;???/*?total?size?of?DT?block?*/ 此字段應(yīng)包含結(jié)構(gòu)塊從標(biāo)題開始的字節(jié)偏移 ?uint32_t?off_dt_struct;???/*?offset?to?structure?*/ 此字段應(yīng)包含從標(biāo)題開始的字符串塊的字節(jié)偏移量 ?uint32_t?off_dt_strings;??/*?offset?to?strings?*/ 此字段應(yīng)包含從標(biāo)題開始的內(nèi)存保留塊的字節(jié)偏移量 ?uint32_t?off_mem_rsvmap;??/*?offset?to?memory?reserve?map?*/ 此字段應(yīng)包含設(shè)備數(shù)據(jù)結(jié)構(gòu)的版本 ?uint32_t?version;???/*?format?version?*/ 此字段應(yīng)包含設(shè)備所用版本向后兼容的最低版本數(shù)據(jù)結(jié)構(gòu) ?uint32_t?last_comp_version;??/*?last?compatible?version?*/ ?/*?version?2?fields?below?*/ 此字段應(yīng)包含系統(tǒng)引導(dǎo)CPU的物理ID。它應(yīng)與設(shè)備樹中CPU節(jié)點(diǎn)的reg屬性中給定的物理ID相同 ?uint32_t?boot_cpuid_phys;??/*?Which?physical?CPU?id?we're?booting?on?*/ ?/*?version?3?fields?below?*/ 此字段應(yīng)包含字符串塊部分的字節(jié)長(zhǎng)度 ?uint32_t?size_dt_strings;??/*?size?of?the?strings?block?*/ ?/*?version?17?fields?below?*/ 此字段應(yīng)包含結(jié)構(gòu)塊部分的字節(jié)長(zhǎng)度 ?uint32_t?size_dt_struct;??/*?size?of?the?structure?block?*/ };
?
1.4.2.memory reservation block
內(nèi)存保留塊向客戶端程序提供物理內(nèi)存中被保留的區(qū)域的列表,這些內(nèi)存不用于一般的內(nèi)存分配,目的是保護(hù)重要的數(shù)據(jù)結(jié)構(gòu)不被客戶端程序覆蓋。這個(gè)區(qū)域包括了若干的reserve memory描述符。每個(gè)reserve memory描述符是由address和size組成。其中address和size都是用U64來(lái)描述:
?
struct?fdt_reserve_entry?{ uint64_t?address; uint64_t?size; };
?
1.4.3.Structure block
結(jié)構(gòu)塊描述了設(shè)備樹本身的結(jié)構(gòu)和內(nèi)容。它由若干的分片組成,每個(gè)分片開始位置都是保存了令牌(token),以此來(lái)描述該分片的屬性和內(nèi)容。
FDT_BEGIN_NODE (0x00000001):該token描述了一個(gè)node的開始位置,緊挨著該token的就是node name(包括unit address)
FDT_END_NODE (0x00000002):該token描述了一個(gè)node的結(jié)束位置
FDT_PROP (0x00000003):該token描述了一個(gè)property的開始位置,該token之后是兩個(gè)u32的數(shù)據(jù)。它們之后就是長(zhǎng)度為len的具體的屬性值數(shù)據(jù)。
?
struct?{ uint32_t?len;?表示該property value data的size。 uint32_t?nameoff;?表示該屬性字符串在device?tree?strings?block的偏移值 }
?
FDT_NOP (0x00000004):被解析設(shè)備樹的程序忽略,可用于覆蓋其他屬性,以刪除它
FDT_END (0x00000009):標(biāo)記結(jié)構(gòu)塊的結(jié)束所以,一個(gè)DTB的結(jié)構(gòu)塊可能如下:
?
(optionally)?any?number?of?FDT_NOP?tokens FDT_BEGIN_NODE?token: --node’s?name --paddings For?each?property?of?the?node: ???????????????--FDT_NOP(optionally) ???????????????--FDT_PROP?token? ???????????????????--property???? all?child?nodes?in?this?format (optionally)?any?number?of?FDT_NOP?tokens FDT_END_NODE?token
?
1.4.4.Strings Block
定義了各個(gè)node中使用的屬性的字符串表。由于很多屬性會(huì)出現(xiàn)在多個(gè)node中,因此,所有的屬性字符串組成了一個(gè)string block。這樣可以壓縮DTB的size。
1.5.Linux解析設(shè)備樹
設(shè)備樹描述了設(shè)備的詳細(xì)信息,這些信息包括數(shù)字類型的、字符串類型的、數(shù)組類型的,我們?cè)诰帉戲?qū)動(dòng)時(shí)需要去獲取這些信息。Linux內(nèi)核提供一系列以of_開頭的函數(shù)來(lái)獲取設(shè)備樹信息,這些函數(shù)的原型都定義在include/linux/of.h中。設(shè)備以節(jié)點(diǎn)的形式掛在設(shè)備樹上,Linux內(nèi)核使用device_node結(jié)構(gòu)體來(lái)描述一個(gè)節(jié)點(diǎn),其定義在include/linux/of.h中:
?
struct?device_node?{ ?const?char?*name;?????device?node?name ?const?char?*type;?????對(duì)應(yīng)device_type的屬性 ?phandle?phandle;??????對(duì)應(yīng)該節(jié)點(diǎn)的phandle屬性 ?const?char?*full_name;??從“/”開始的,表示該node的full?path ?Struct??property?*properties;??????該節(jié)點(diǎn)的屬性列表 如果需要?jiǎng)h除某些屬性,kernel并非真的刪除,而是掛入到deadprops的列表 ?struct??property?*deadprops;?/*?removed?properties?*/ parent、child以及sibling將所有的device?node連接起來(lái) ?Struct??device_node?*parent;?? ?Struct??device_node?*child; ?Struct??device_node?*sibling; 通過(guò)該指針可以獲取相同類型的下一個(gè)node ?Struct??device_node?*next;?/*?next?device?of?same?type?*/ 通過(guò)該指針可以獲取node?global?list下一個(gè)node ?struct??device_node?*allnext;?/*?next?in?list?of?all?nodes?*/ ?struct??kobject?kobj; ?unsigned?long?_flags; ?void?*data; #if?defined(CONFIG_SPARC) ?const?char?*path_component_name; ?unsigned?int?unique_id; ?struct?of_irq_controller?*irq_trans; #endif };
?
1.5.1.查找節(jié)點(diǎn)的 OF函數(shù)
1.5.1.1.of_find_node_by_name
功能:Find a node by its "name" property函數(shù)
?
struct?device_node?*of_find_node_by_name(struct?device_node?*from, ?const?char?*name)
?
參數(shù):
?
@from:開始查找的節(jié)點(diǎn),如果為NULL表示從根節(jié)點(diǎn)開始查找整個(gè)設(shè)備樹。 @name::要查找的節(jié)點(diǎn)名字。
?
返回值:找到的節(jié)點(diǎn),如果為NULL表示查找失敗。
1.5.1.2.of_find_node_by_path
功能:Find a node matching a full OF path函數(shù):
?
struct?device_node?*of_find_node_by_path(const?char?*path)
?
參數(shù):@path:完整的匹配路徑返回值:找到的節(jié)點(diǎn),如果為NULL表示查找失敗。
1.5.1.3.of_find_node_by_type
功能Find a node by its "device_type" property函數(shù)
?
struct?device_node?*of_find_node_by_type(struct?device_node?*from, ?const?char?*type)
?
參數(shù)
?
@from:開始查找的節(jié)點(diǎn),如果為NULL表示從根節(jié)點(diǎn)開始查找整個(gè)設(shè)備樹 @type:?要查找的節(jié)點(diǎn)類型
?
返回值找到的節(jié)點(diǎn),如果為NULL表示查找失敗。
1.5.1.4.of_find_compatible_node
功能通過(guò)device_type和compatible查找指定節(jié)點(diǎn)函數(shù)
?
struct?device_node?*of_find_compatible_node(struct?device_node?*from,const?char?*type,?const?char?*compatible)
?
參數(shù)
?
@from:開始查找的節(jié)點(diǎn),如果為NULL表示從根節(jié)點(diǎn)開始查找整個(gè)設(shè)備樹 @type:?要查找的節(jié)點(diǎn)device_type屬性 @compatible:節(jié)點(diǎn)的compatible屬性列表
?
返回值找到的節(jié)點(diǎn),如果為NULL表示查找失敗。
1.5.1.5.of_find_node_with_property
功能通過(guò)屬性名查找指定節(jié)點(diǎn)函數(shù)
?
struct?device_node?*of_find_node_with_property(struct?device_node?*from,const?char?*prop_name)
?
參數(shù)
?
@from:開始查找的節(jié)點(diǎn),如果為NULL表示從根節(jié)點(diǎn)開始查找整個(gè)設(shè)備樹 @type:?要查找的節(jié)點(diǎn)屬性名稱
?
返回值找到的節(jié)點(diǎn),如果為NULL表示查找失敗。
1.5.2.查找父 /子節(jié)點(diǎn)的 OF函數(shù)
1.5.2.1.of_get_parent
功能函數(shù)用于獲取指定節(jié)點(diǎn)的父節(jié)點(diǎn)(如果有父節(jié)點(diǎn)的話 )函數(shù)
?
struct?device_node?*of_get_parent(const?struct?device_node?*node)
?
參數(shù)
?
@node:要查找父節(jié)點(diǎn)的節(jié)點(diǎn)
?
返回值找到的父節(jié)點(diǎn)
1.5.2.2.of_get_next_available_child
功能獲取子節(jié)點(diǎn),并跳過(guò)status = "disabled"的節(jié)點(diǎn)函數(shù)
?
struct?device_node?*of_get_next_available_child(const?struct?device_node?*node,struct?device_node?*prev)
?
參數(shù)
?
@node:?父節(jié)點(diǎn) @prev:當(dāng)前父節(jié)點(diǎn)的上一個(gè)子節(jié)點(diǎn),?如果為空,則獲取第一個(gè)子節(jié)點(diǎn)
?
返回值找到的子節(jié)點(diǎn)
1.5.3.提取屬性值的 OF函數(shù)
Linux內(nèi)核使用struct property來(lái)保存節(jié)點(diǎn)的屬性,其定義在/include/linux/of.h中:
?
struct?property?{ ?char??*name;??????屬性的名稱 ?int??length;??????屬性的長(zhǎng)度 ?void??*value;?????屬性的值 ?struct?property?*next;???下一個(gè)屬性 ?unsigned?long?_flags;????? ?unsigned?int?unique_id; ?struct?bin_attribute?attr; };
?
1.5.3.1.of_find_property
功能尋找指定的屬性函數(shù)
?
struct?property?*of_find_property(const?struct?device_node?*np, ??????const?char?*name, ??????int?*lenp)
?
參數(shù)
?
@np:?設(shè)備節(jié)點(diǎn) @name:屬性名稱 @lenp:屬性的字節(jié)數(shù)
?
返回值找到的屬性
1.5.3.2.讀取屬性中u8、u16、u32和u64類型的數(shù)組數(shù)據(jù)
當(dāng)設(shè)置sz為1時(shí),就是讀取一個(gè)數(shù)據(jù),Linux內(nèi)核也是這么封裝的。
?
int?of_property_read_u8_array(const?struct?device_node?*np, ????????const?char?*propname,?u8?*out_values,?size_t?sz) int?of_property_read_u16_array(const?struct?device_node?*np, ????????const?char?*propname,?u16?*out_values,?size_t?sz) int?of_property_read_u32_array(const?struct?device_node?*np, ????????const?char?*propname,?u32?*out_values,size_t?sz) int?of_property_read_u64(const?struct?device_node?*np,?const?char?*propname, ????????u64?*out_value)
?
1.5.3.3.of_property_read_string
功能找到并讀取屬性字符串函數(shù)
?
int?of_property_read_string(struct?device_node?*np,?const?char?*propname,const?char?**out_string)
?
參數(shù)
?
@np:?設(shè)備節(jié)點(diǎn) @propname:屬性名稱 @out_string:讀取的字符串
?
返回值
?
0:讀取成功 -EINVAL:屬性不存在 -ENODATA:屬性沒(méi)有這個(gè)值 -EILSEQ:字符串不是以空字符’0’結(jié)尾
?
2.設(shè)備樹解析流程
2.1.內(nèi)核啟動(dòng)并獲取設(shè)備樹
在uboot引導(dǎo)內(nèi)核的時(shí)候,會(huì)將設(shè)備樹在物理內(nèi)存中的物理起始內(nèi)存地址傳遞給Linux內(nèi)核,然后Linux內(nèi)核在unflattern_device_tree中解析設(shè)備鏡像,并利用掃描到的信息創(chuàng)建由device node構(gòu)成的鏈表,全局變量of_allnodes指向鏈表的根節(jié)點(diǎn),設(shè)備樹的每一個(gè)節(jié)點(diǎn)都由一個(gè)struct device_node與之對(duì)應(yīng)。unflatten_device_tree的意思是解開設(shè)備樹,在這個(gè)函數(shù)里調(diào)用了__unflatten_device_tree這一函數(shù):
?
/** ?*?__unflatten_device_tree?-?create?tree?of?device_nodes?from?flat?blob ?* ?*?unflattens?a?device-tree,?creating?the ?*?tree?of?struct?device_node.?It?also?fills?the?"name"?and?"type" ?*?pointers?of?the?nodes?so?the?normal?device-tree?walking?functions ?*?can?be?used. ?*?@blob:?The?blob?to?expand ?*?@mynodes:?The?device_node?tree?created?by?the?call ?*?@dt_alloc:?An?allocator?that?provides?a?virtual?address?to?memory ?*?for?the?resulting?tree ?*/ static?void?__unflatten_device_tree(struct?boot_param_header?*blob, ????????struct?device_node?**mynodes, ????????void?*?(*dt_alloc)(u64?size,?u64?align))
?
所以,現(xiàn)在為止,我們得到了一個(gè)名為of_allnodes的struct *device_node,它指向了設(shè)備樹展開后的device_node樹,后續(xù)的操作都是基于device_node樹。
2.2.創(chuàng)建platform_device
內(nèi)核從啟動(dòng)到創(chuàng)建設(shè)備的過(guò)程大致如下:在do_initcalls中會(huì)傳遞level給do_initcall_level來(lái)調(diào)用不同層次的初始化函數(shù),level的對(duì)應(yīng)關(guān)系見linux-3.10/include/linux/init.h 第196行。在這個(gè)初始化過(guò)程中,會(huì)調(diào)用一個(gè)customize_machine的函數(shù)。
2.3.Platform driver注冊(cè)流程
此節(jié)分析Platform driver的注冊(cè)流程,以memctrl驅(qū)動(dòng)的注冊(cè)為例分析。關(guān)于系統(tǒng)調(diào)用驅(qū)動(dòng)初始化函數(shù)的流程分析,參考自動(dòng)初始化機(jī)制章節(jié)。本章節(jié)分析從設(shè)備驅(qū)動(dòng)文件的xxx_init函數(shù)開始分析。
2.3.1.struct platform_driver
platform_driver是在device_driver之上的一層封裝,其結(jié)構(gòu)如下:
?
struct?platform_driver?{ ?int?(*probe)(struct?platform_device?*);???探測(cè)函數(shù) ?int?(*remove)(struct?platform_device?*);??驅(qū)動(dòng)卸載時(shí)執(zhí)行 ?void?(*shutdown)(struct?platform_device?*);??關(guān)機(jī)時(shí)執(zhí)行函數(shù) ?int?(*suspend)(struct?platform_device?*,?pm_message_t?state);??掛起函數(shù) ?int?(*resume)(struct?platform_device?*);?????恢復(fù)函數(shù) ?struct?device_driver?driver;???????????管理的driver對(duì)象 ?const?struct?platform_device_id?*id_table;???匹配時(shí)使用 };
?
2.3.2.struct device_driver
struct device_driver是系統(tǒng)提供的基本驅(qū)動(dòng)結(jié)構(gòu):
?
struct?device_driver?{ ?const?char???*name;??驅(qū)動(dòng)名稱 ?struct?bus_type???*bus;?所屬總線 ?struct?module???*owner;?模塊擁有者 ?const?char???*mod_name;?內(nèi)建的模塊使用 ?bool?suppress_bind_attrs;??是否綁定到sysfs ?const?struct?of_device_id??*of_match_table;?設(shè)備樹匹配表 ?const?struct?acpi_device_id??*acpi_match_table;?ACPI匹配表 ?int?(*probe)?(struct?device?*dev);??探測(cè)設(shè)備 ?int?(*remove)?(struct?device?*dev);?與設(shè)備脫離時(shí)調(diào)用 ?void?(*shutdown)?(struct?device?*dev);?在關(guān)機(jī)時(shí)關(guān)閉設(shè)備 ?int?(*suspend)?(struct?device?*dev,?pm_message_t?state);?使設(shè)備進(jìn)入睡眠模式調(diào)用 ?int?(*resume)?(struct?device?*dev);??喚醒設(shè)備時(shí)調(diào)用 ?const?struct?attribute_group?**groups;?自動(dòng)創(chuàng)建的默認(rèn)屬性組 ?const?struct?dev_pm_ops?*pm;??設(shè)備的功耗管理 ?struct?driver_private?*p;?驅(qū)動(dòng)的私有數(shù)據(jù) };
?
2.3.3.platform_driver_register
Platform_driver的注冊(cè)接口是platform_driver_register,其定義如下:
?
int?platform_driver_register(struct?platform_driver?*drv) { ?drv->driver.bus?=?&platform_bus_type;??設(shè)置總線類型 ?if?(drv->probe)????確認(rèn)定義了probe函數(shù)????? ??drv->driver.probe?=?platform_drv_probe;??里面實(shí)際調(diào)用的是drv的probe函數(shù) ?if?(drv->remove) ??drv->driver.remove?=?platform_drv_remove; ?if?(drv->shutdown) ??drv->driver.shutdown?=?platform_drv_shutdown; ?return?driver_register(&drv->driver); }
?
platform_driver_register接口是為注冊(cè)總線驅(qū)動(dòng)做一些準(zhǔn)備工作,定義了總線類型,設(shè)置了driver的部分接口,最后driver_register會(huì)向總線注冊(cè)驅(qū)動(dòng)
2.3.4.driver_register
?
int?driver_register(struct?device_driver?*drv) { ?int?ret; ?struct?device_driver?*other; ?BUG_ON(!drv->bus->p); ?if?((drv->bus->probe?&&?drv->probe)?|| ?????(drv->bus->remove?&&?drv->remove)?|| ?????(drv->bus->shutdown?&&?drv->shutdown)) ??printk(KERN_WARNING?"Driver?'%s'?needs?updating?-?please?use?" ???"bus_type?methods ",?drv->name); ?other?=?driver_find(drv->name,?drv->bus);?檢查驅(qū)動(dòng)是否已經(jīng)注冊(cè) ?if?(other)?{ ??printk(KERN_ERR?"Error:?Driver?'%s'?is?already?registered,?" ???"aborting... ",?drv->name); ??return?-EBUSY; ?} ?ret?=?bus_add_driver(drv);???driver_register的主要工作放在了這里 ?if?(ret) ??return?ret; ?ret?=?driver_add_groups(drv,?drv->groups);?主要是在sysfs添加驅(qū)動(dòng)屬性 ?if?(ret)?{ ??bus_remove_driver(drv); ??return?ret; ?} ?kobject_uevent(&drv->p->kobj,?KOBJ_ADD);???涉及到uevent,暫時(shí)不分析 ?return?ret; }
?
2.3.5.bus_add_driver
由以上分析可知,驅(qū)動(dòng)的注冊(cè),重點(diǎn)在bus_add_driver()函數(shù),它會(huì)向總線添加驅(qū)動(dòng):
?
Drivers/base/bus.c int?bus_add_driver(struct?device_driver?*drv) { ?struct?bus_type?*bus; ?struct?driver_private?*priv;??包含與驅(qū)動(dòng)相關(guān)的kobject和klist結(jié)構(gòu) ?int?error?=?0; ?bus?=?bus_get(drv->bus);??獲取設(shè)備所屬的總線類型 ?if?(!bus) ??return?-EINVAL; ?pr_debug("bus:?'%s':?add?driver?%s ",?bus->name,?drv->name); ?priv?=?kzalloc(sizeof(*priv),?GFP_KERNEL); ?if?(!priv)?{ ??error?=?-ENOMEM; ??goto?out_put_bus; ?} ?klist_init(&priv->klist_devices,?NULL,?NULL); ?priv->driver?=?drv; ?drv->p?=?priv; ?priv->kobj.kset?=?bus->p->drivers_kset; ?error?=?kobject_init_and_add(&priv->kobj,?&driver_ktype,?NULL, ?????????"%s",?drv->name); ?if?(error) ??goto?out_unregister; ?klist_add_tail(&priv->knode_bus,?&bus->p->klist_drivers); ?if?(drv->bus->p->drivers_autoprobe)?{?如果設(shè)置了自動(dòng)探測(cè) ??error?=?driver_attach(drv); ??if?(error) ???goto?out_unregister; ?} ?module_add_driver(drv->owner,?drv); ?error?=?driver_create_file(drv,?&driver_attr_uevent); ?if?(error)?{ ??printk(KERN_ERR?"%s:?uevent?attr?(%s)?failed ", ???__func__,?drv->name); ?} ?error?=?driver_add_attrs(bus,?drv); ?if?(error)?{ ??/*?How?the?hell?do?we?get?out?of?this?pickle??Give?up?*/ ??printk(KERN_ERR?"%s:?driver_add_attrs(%s)?failed ", ???__func__,?drv->name); ?} ?if?(!drv->suppress_bind_attrs)?{ ??error?=?add_bind_files(drv); ??if?(error)?{ ???/*?Ditto?*/ ???printk(KERN_ERR?"%s:?add_bind_files(%s)?failed ", ????__func__,?drv->name); ??} ?} ?return?0; out_unregister: ?kobject_put(&priv->kobj); ?kfree(drv->p); ?drv->p?=?NULL; out_put_bus: ?bus_put(bus); ?return?error; }
?
2.3.6.driver_attach
driver_attach會(huì)嘗試綁定設(shè)備和驅(qū)動(dòng)。編譯總線上的所有設(shè)備,然驅(qū)動(dòng)挨個(gè)嘗試匹配,如果driver_probe_device()返回0且@dev->driver被設(shè)置,就代表找到了一對(duì)兼容的設(shè)備驅(qū)動(dòng)。
?
int?driver_attach(struct?device_driver?*drv) { ?return?bus_for_each_dev(drv->bus,?NULL,?drv,?__driver_attach); } EXPORT_SYMBOL_GPL(driver_attach);
?
2.3.7.__driver_attach
對(duì)于每一個(gè)總線的設(shè)備,driver_attach都會(huì)調(diào)用__driver_attach來(lái)嘗試與驅(qū)動(dòng)匹配。
?
static?int?__driver_attach(struct?device?*dev,?void?*data) { ?struct?device_driver?*drv?=?data; ?/* ??*?Lock?device?and?try?to?bind?to?it.?We?drop?the?error ??*?here?and?always?return?0,?because?we?need?to?keep?trying ??*?to?bind?to?devices?and?some?drivers?will?return?an?error ??*?simply?if?it?didn't?support?the?device. ??* ??*?driver_probe_device()?will?spit?a?warning?if?there ??*?is?an?error. ??*/ ?if?(!driver_match_device(drv,?dev))??匹配設(shè)備和驅(qū)動(dòng),這里調(diào)用的是platform_match ??return?0; ?if?(dev->parent)?/*?Needed?for?USB?*/ ??device_lock(dev->parent); ?device_lock(dev);??設(shè)置互斥鎖,防止其他進(jìn)程訪問(wèn)設(shè)備資源 ?if?(!dev->driver)?? 如果設(shè)備沒(méi)有驅(qū)動(dòng),則為設(shè)備探測(cè)驅(qū)動(dòng),這個(gè)函數(shù)與注冊(cè)設(shè)備調(diào)用的是同一個(gè)函數(shù) ??driver_probe_device(drv,?dev);?? ?device_unlock(dev); ?if?(dev->parent) ??device_unlock(dev->parent); ?return?0; }
?
driver_probe_device里調(diào)用really_probe函數(shù),并在really_probe中調(diào)用驅(qū)動(dòng)文件中的probe函數(shù),對(duì)于memctrl驅(qū)動(dòng)而言,就是xxxx_memctrl_probe函數(shù)。至此,platfprm driver就注冊(cè)好了。
2.4.Platform Bus的匹配原則
由以上的代碼分析得知,注冊(cè)platform device時(shí),會(huì)調(diào)用__device_attach -> driver_match_device,注冊(cè)platform driver時(shí),會(huì)調(diào)用__driver_attach -> driver_match_device,也就是說(shuō)設(shè)備和驅(qū)動(dòng)都會(huì)調(diào)用到這個(gè)函數(shù):
?
static?inline?int?driver_match_device(struct?device_driver?*drv, ??????????struct?device?*dev) { ?return?drv->bus->match???drv->bus->match(dev,?drv)?:?1; }
?
drv->bus->match,這是驅(qū)動(dòng)綁定的總線提供的匹配函數(shù),這里注冊(cè)的是platform總線設(shè)備,而platform總線的定義參考3.2.6 platform_bus_type。Platform對(duì)應(yīng)的match函數(shù)為:platform_match:
?
static?int?platform_match(struct?device?*dev,?struct?device_driver?*drv) { ?struct?platform_device?*pdev?=?to_platform_device(dev); ?struct?platform_driver?*pdrv?=?to_platform_driver(drv); ?/*?Attempt?an?OF?style?match?first?*/ ?if?(of_driver_match_device(dev,?drv)) ??return?1; ?/*?Then?try?ACPI?style?match?*/ ?if?(acpi_driver_match_device(dev,?drv)) ??return?1; ?/*?Then?try?to?match?against?the?id?table?*/ ?if?(pdrv->id_table) ??return?platform_match_id(pdrv->id_table,?pdev)?!=?NULL; ?/*?fall-back?to?driver?name?match?*/ ?return?(strcmp(pdev->name,?drv->name)?==?0); }
?
2.4.1.of_driver_match_device
根據(jù)驅(qū)動(dòng)的of_match_table判斷是否有驅(qū)動(dòng)與之匹配。對(duì)memctrl驅(qū)動(dòng)而言,其of_match_table如下:
?
static?struct?of_device_id?xxxx_memctrl_of_match[]?=?{ ?{?.compatible?=?"xxxx,memctrl",?}, ???{}, };
?
of_driver_match_device的執(zhí)行流程如下:
所以重點(diǎn)應(yīng)該在__of_match_node函數(shù):
2.4.1.1.__of_match_node
?
static?const?struct?of_device_id?*__of_match_node(const?struct?of_device_id?*matches,?const?struct?device_node?*node) { ?if?(!matches) ??return?NULL; ?while?(matches->name[0]?||?matches->type[0]?||?matches->compatible[0])?{ ??int?match?=?1; ??if?(matches->name[0])???查找名字 ???????match?&=?node->name?&&?!strcmp(matches->name,?node->name); ??if?(matches->type[0])???查找類型 ???????match?&=?node->type?&&?!strcmp(matches->type,?node->type); ??if?(matches->compatible[0])??查找屬性,檢測(cè)節(jié)點(diǎn)的compatible是否與驅(qū)動(dòng)的一致 ???????match?&=?__of_device_is_compatible(node,?matches->compatible); ??if?(match) ???????return?matches; ??????matches++; ?} ?return?NULL; }
?
3.使用設(shè)備資源
4.自動(dòng)初始化機(jī)制
4.1.編譯到內(nèi)核
4.1.1.module_init宏展開
Linux中每一個(gè)模塊都有一個(gè)module_init函數(shù),并且有且只有一個(gè),其定義如下:
?
/** ?*?module_init()?-?driver?initialization?entry?point ?*?@x:?function?to?be?run?at?kernel?boot?time?or?module?insertion ?*? ?*?module_init()?will?either?be?called?during?do_initcalls()?(if ?*?builtin)?or?at?module?insertion?time?(if?a?module).??There?can?only ?*?be?one?per?module. ?*/ #define?module_init(x)?__initcall(x);
__initcall(x)定義如下:
#define?__initcall(fn)?device_initcall(fn)
?
device_initcall(fn)定義如下:
?
#define?device_initcall(fn)???????__define_initcall(fn,?6)
?
__define_initcall的定義如下:
?
/*?initcalls?are?now?grouped?by?functionality?into?separate? ?*?subsections.?Ordering?inside?the?subsections?is?determined ?*?by?link?order.? ?*?For?backwards?compatibility,?initcall()?puts?the?call?in? ?*?the?device?init?subsection. ?* ?*?The?`id'?arg?to?__define_initcall()?is?needed?so?that?multiple?initcalls ?*?can?point?at?the?same?handler?without?causing?duplicate-symbol?build?errors. ?*/ ? #define?__define_initcall(fn,?id)? ?static?initcall_t?__initcall_##fn##id?__used? ?__attribute__((__section__(".initcall"?#id?".init")))?=?fn
?
Initcalls現(xiàn)在按照功能分組到單獨(dú)的子部分。子部分內(nèi)部的順序由鏈接順序決定。為了向后兼容,initcall()將調(diào)用放到device init小節(jié)中。需要定義initcall()的’id’參數(shù),以便多個(gè)initcall可以指向同一個(gè)處理程序,而不會(huì)導(dǎo)致重復(fù)符號(hào)構(gòu)建錯(cuò)誤。若不理解上述代碼的用法,可以參考__attribute__的section用法和C語(yǔ)言宏定義中#和##的用法。所以將__define_initcall展開將會(huì)是下面的內(nèi)容:
?
假設(shè)__define_initcall(led_init,?6) Static?initcall_t?__initcall_led_init6?__used? __attribute__((__section__(".initcall6.init")))?=?led_init
?
即是定義了一個(gè)類型為initcall_t的函數(shù)指針變量__initcall_led_init6,并賦值為led_init,該變量在鏈接時(shí)會(huì)鏈接到section(.initcall6.init)。
4.1.2.鏈接腳本
在linux3.10/arch/arm/kernel/vmlinux.lds.S中:
?
...... SECTIONS??/*?line?54?*/ { ...... ?.init.data?:?{?/*?line?202?*/ #ifndef?CONFIG_XIP_KERNEL ??INIT_DATA #endif ??INIT_SETUP(16) ??INIT_CALLS ??CON_INITCALL ??SECURITY_INITCALL ??INIT_RAM_FS ?} ...... }
?
在linux3.10/include/asm-generic/vmlinux.lds.h中:
?
#define?VMLINUX_SYMBOL(x)?__VMLINUX_SYMBOL(x) #define?__VMLINUX_SYMBOL(x)?x ......?/*?line?664?*/ #define?INIT_CALLS_LEVEL(level)?????? ??VMLINUX_SYMBOL(__initcall##level##_start)?=?.;?? ??*(.initcall##level##.init)???? ??*(.initcall##level##s.init)???? #define?INIT_CALLS??????? ??VMLINUX_SYMBOL(__initcall_start)?=?.;??? ??*(.initcallearly.init)????? ??INIT_CALLS_LEVEL(0)????? ??INIT_CALLS_LEVEL(1)????? ??INIT_CALLS_LEVEL(2)????? ??INIT_CALLS_LEVEL(3)????? ??INIT_CALLS_LEVEL(4)????? ??INIT_CALLS_LEVEL(5)????? ??INIT_CALLS_LEVEL(rootfs)???? ??INIT_CALLS_LEVEL(6)????? ??INIT_CALLS_LEVEL(7)????? ??VMLINUX_SYMBOL(__initcall_end)?=?.; ......
?
所以INIT_CALLS_LEVEL(6)會(huì)展開為:
?
__initcall6_start?=?.;??*(.initcall6.init)???*(.initcall6s.init)
?
所以__initcall_led_init6會(huì)鏈接到
?
section(.initcall6.init)
?
4.1.3.初始化
內(nèi)核啟動(dòng)流程為:
?
do_initcall_level的主要內(nèi)容如下:
?
/*?linux3.10/init/main.c?line?744?*/ static?void?__init?do_initcall_level(int?level) { ..... ?for?(fn?=?initcall_levels[level];?fn??
由代碼可知,內(nèi)核會(huì)依次調(diào)用level段存儲(chǔ)的初始化函數(shù)。比如對(duì)于模塊來(lái)說(shuō)level等于6。
4.2.動(dòng)態(tài)加載的模塊(.ko)
4.2.1.Module_init展開
如果設(shè)置為編譯成動(dòng)態(tài)加載的模塊(.ko),module_init的展開形式與編譯到內(nèi)核不一樣。
?
/*?Each?module?must?use?one?module_init().?*/ #define?module_init(initfn)????? ?static?inline?initcall_t?__inittest(void)?????檢查定義的函數(shù)是否符合initcall_t類型 ?{?return?initfn;?}????? ?int?init_module(void)?__attribute__((alias(#initfn)));?
alias屬性是GCC的特有屬性,將定義init_module為函數(shù)initfn的別名,所以module_init(initfn)的作用就是定義一個(gè)變量名init_module,其地址和initfn是一樣的。
4.2.2.*mod.c文件
編譯成module的模塊都會(huì)自動(dòng)產(chǎn)生一個(gè)*.mod.c的文件,例如:
?
struct?module?__this_module __attribute__((section(".gnu.linkonce.this_module")))?=?{ .name?=?KBUILD_MODNAME, .init?=?init_module, #ifdef?CONFIG_MODULE_UNLOAD .exit?=?cleanup_module, #endif .arch?=?MODULE_ARCH_INIT, };?
即定義了一個(gè)類型為module的全局變量__this_module,其成員.init就是上文由module_init定義的init_module變量。并且__this_module會(huì)被鏈接到section(".gnu.linkonce.this_module")。
4.2.3.動(dòng)態(tài)加載
insmod是busybox提供的用戶層命令:路徑busybox/modutils/ insmod.c
?
insmod_main bb_init_module init_module?
路徑busybox/modutils/modutils.c:
?
#define?init_module(mod,?len,?opts)?. syscall(__NR_init_module,?mod,?len,?opts)該系統(tǒng)調(diào)用對(duì)應(yīng)內(nèi)核層的sys_init_module函數(shù)?
路徑:kernel/module.c
?
SYSCALL_DEFINE3(init_module,…) //加載模塊的ko文件,并解釋各個(gè)section,重定位 mod?=?load_module(umod,?len,?uargs); //查找section(".gnu.linkonce.this_module") modindex?=?find_sec(hdr,?sechdrs,?secstrings,".gnu.linkonce.this_module"); //找到Hello_module.mod.c定義的module數(shù)據(jù)結(jié)構(gòu) mod?=?(void?*)sechdrs[modindex].sh_addr; if?(mod->init?!=?NULL) ret?=?do_one_initcall(mod->init);?//調(diào)用initfn.?
4.3.__attribute__的section用法
__define_initcall使用了gcc的__attribute__眾多屬性中的section子項(xiàng),其使用方式為:
?
__attribute__((__section__("section_name")))?
其作用是將作用的函數(shù)或數(shù)據(jù)放入指定的名為”section_name”的段。
4.4. C語(yǔ)言宏定義中#和##的用法
4.4.1.一般用法
我們使用#把宏參數(shù)變?yōu)橐粋€(gè)字符串。
?
#define?PRINT(FORMAT,VALUE) printf("The?value?of"#VALUE"is?"?FORMAT" ",VALUE)??
調(diào)用:printf("%d",x+3); ? ? --> ? ? 打印:The value of x+3 is 20
這是因?yàn)椤盩he value of”#VALUE”is ” FORMAT” ”實(shí)際上是包含了”The value of “,#VALUE,”is “,FORMAT,” ” 五部分字符串,其中VALUE和FORMAT被宏參數(shù)的實(shí)際值替換了。
用##把兩個(gè)宏參數(shù)貼合在一起
?
#define?ADD_TO_SUM(sum_number,val)?sum##sum_bumber+=(val)?
調(diào)用:ADD_TO_SUM(2,100); ? ? --> ? ? 打印:sum2+=(100)
需要注意的是凡宏定義里有用'#'或'##'的地方宏參數(shù)是不會(huì)再展開。
4.4.2.'#'和'##'的一些應(yīng)用特例
合并匿名變量名
?
#define??___ANONYMOUS1(type,?var,?line)??type??var##line? #define??__ANONYMOUS0(type,?line)??___ANONYMOUS1(type,?_anonymous,?line)? #define??ANONYMOUS(type)??__ANONYMOUS0(type,?__LINE__)??
例:ANONYMOUS(static int); ?即 static int _anonymous70; ?70表示該行行號(hào);第一層:ANONYMOUS(static int); --> ?__ANONYMOUS0(static int, LINE);第二層:? ? ? ? ? ? ? ? ? ? ? ? ? ?--> ?___ANONYMOUS1(static int, _anonymous, 70);第三層:? ? ? ? ? ? ? ? ? ? ? ? ? ?--> ?static int ?_anonymous70;即每次只能解開當(dāng)前層的宏,所以__LINE__在第二層才能被解開;
填充結(jié)構(gòu)
?
#define??FILL(a)???{a,?#a}? enum?IDD{OPEN,?CLOSE};? typedef?struct?MSG{? ??IDD?id;? ??const?char??msg;? }MSG;? MSG?_msg[]?=?{FILL(OPEN),?FILL(CLOSE)};??
相當(dāng)于:
?
MSG?_msg[]?=?{{OPEN,?OPEN},? ??????????????{CLOSE,?CLOSE}};??
記錄文件名
?
#define??_GET_FILE_NAME(f)???#f? #define??GET_FILE_NAME(f)????_GET_FILE_NAME(f)? static?char??FILE_NAME[]?=?GET_FILE_NAME(__FILE__);??
得到一個(gè)數(shù)值類型所對(duì)應(yīng)的字符串緩沖大小
?
#define??_TYPE_BUF_SIZE(type)??sizeof?#type? #define??TYPE_BUF_SIZE(type)???_TYPE_BUF_SIZE(type)? char??buf[TYPE_BUF_SIZE(INT_MAX)];? ?????--??char??buf[_TYPE_BUF_SIZE(0x7fffffff)];? ?????--??char??buf[sizeof?0x7fffffff];??
這里相當(dāng)于:
?
char??buf[11];- END -?
?
審核編輯:劉清
評(píng)論