1.設備樹概念
1.1.設備樹感性認識
設備樹(Device Tree),將這個詞分開就是“設備”和“樹”,描述設備樹的文件叫做DTS(Device Tree Source),這個DTS 文件采用樹形結構描述板級設備,比如CPU 數量、 內存基地址、IIC 接口上接了哪些設備、SPI 接口上接了哪些設備等等。設備樹是樹形數據結構,具有描述系統中設備的節點。每個節點都有描述所代表設備特征的鍵值對。每個節點只有一個父節點,而根節點則沒有父節點。
1.2.DTS、DTB、DTC
DTS:設備樹源碼文件;DTB:將DTS編譯后得到的二進制文件;DTC:DTS的編譯工具,其源碼在內核的scriptsdtc目錄下。基于同樣arm架構的CPU有很多,同一個CPU會制作很多配置不一的板子,如何正確的編譯所選的板子的DTS文件呢?在內核的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的開發板,只要設置CONFIG_ARCH_xxx=y,所有用到這顆SOC的DTS都會編譯成DTB。如果后續還用到了這顆SOC設計的開發板,只要新建一個DTS文件,并將對應名稱的DTB文件名加到dtb-$(CONFIG_ARCH_xxx)中,在編譯設備樹時就會將DTS編譯為二進制的DTB文件。
1.3.Device Tree語法
以下語法分析均以xxx.dts為例。
1.3.1.dtsi頭文件
設備樹的頭文件擴展名為 .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:每一個.dtsi和.dts都有自己的根節點,但是一個設備樹文件只允許有一個根節點,DTC如何處理?將根節點合并,保留最后一級的根節點。包含的頭文件內容會被展開,展開的位置在/memory和/cpus之間。(存疑,只用xxx.dts編譯過)Q2:如果包含過程中有重復的compatible,DTC怎么處理?編譯時不會報錯,會生成兩個compatible屬性一樣的節點。
1.3.2.設備節點
設備樹中的每一個節點都按照以下格式命名:
?
node-name@unit-address
?
node-name表示節點名稱,它的長度范圍應該是1~31個字符,可以由以下的字符組成:
字符 | 說明 |
---|---|
0~9 | 數字 |
a-z | 小寫字母 |
A-Z | 大寫字母 |
, | 逗號(英文) |
. | 句號(英文) |
_ | 下劃線(英文) |
+ | 加號 |
- | 減號 |
表 2-1節點名稱的有效字符
節點名稱應以較低或大寫字符開頭,并應描述設備的一般類別。節點的單位地址特定于節點所在的總線類型。它由表2-1中字符集中的一個或多個ASCII字符組成。單位地址必須與節點的reg屬性中指定的第一個地址匹配。如果節點沒有reg屬性,則必須省略@unit-address,并且單獨使用節點名稱將節點與樹中相同級別的其他節點區分開來。對于reg格式和單位地址,特定總線的綁定可能會指定附加更具體的要求。根節點沒有節點名稱或單位地址。它由正斜杠(/)標識。
圖 2-1節點名稱示例
在圖2-1中,節點名稱為cpu的兩個節點通過uint-address 0和1區分;節點名稱為ethernet的兩個節點通過uint-address fe002000和fe003000區分。在設備樹中經常會看到以下設備名稱:
?
watchdog:?watchdog@04009800?
?
冒號前的是節點標簽(label),冒號后是節點名稱。引入label的目的是方便訪問節點,可以直接通過&label來訪問這個節點。比如上述節點就可以使用&watchdog來訪問。
1.3.2.1.通用名稱建議
節點的名稱應該有些通用,反映設備的功能,而不是其精確的編程模型。如適用,名稱應為以下選擇之一:
?
??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.路徑名稱
通過指定從根節點到所需節點的完整路徑(通過所有子節點),可以唯一識別devicetree中的節點。指定設備路徑的約定是:
?
?/node-name-1/node-name-2/.../node-name-N
?
例如,在圖2-1中,到cpu#1的設備路徑為:
?
/cpus/cpu@1
?
/為根節點,在保證完整路徑明確的前提下,可以省略uint-address。
1.3.3.屬性
設備樹中的每個節點都有描述節點特性的屬性。屬性由名稱和值組成。
1.3.3.1.屬性名稱
屬性名稱的長度范圍應該是1~31個字符,可以由以下的字符組成:
字符 | 說明 |
---|---|
0~9 | 數字 |
a-z | 小寫字母 |
A-Z | 大寫字母 |
, | 逗號(英文) |
. | 句號(英文) |
_ | 下劃線(英文) |
+ | 加號 |
? | 問號(英文) |
# | #號(hash) |
非標準屬性名稱應指定唯一的字符串前綴,例如股票代號,用于標識定義該屬性的公司或組織的名稱。示例:
?
xxx,pin-function?=?<6>; fsl,channel-fifo-len ??linux,network-index ??ibm,ppc-interrupt-server#s
?
1.3.3.2.屬性值
屬性值是包含與屬性關聯的信息的零或多個字節的數組。
數值 | 描述 |
---|---|
、 | 值為空,用于描述bool信息。個人理解類似于flag參數 |
、 | 32位整數,采用big-endian格式。示例:32位值0x11223344將在內存中表示為:地址 ? ?11 ?地址 + 122 ?地址 + 233 ? 地址 + 344 |
、 | 表示采用big-endian格式的64位整數。包括兩個值,其中第一個值包含整數的最有效位,第二個值包含最小有效位。例如:64位值0x1122334455667788將表示為兩個單元格:<0x11223344 0x55667788>。 |
、 | 格式特定于屬性,參見屬性定義。 |
、 | 字符串可打印且以空值(?)結尾。 |
、 | 一個值。phandle值是引用設備樹中另一個節點的方法。可被引用的任何節點都用唯一的定義了phandle屬性數值。該數字用于帶有phandle值類型的屬性值。 |
、 | 串聯在一起的值列表。 |
big-endian和little-endian(大小端):big-endian:是指低地址端存放高位字節;little-endian:是指低地址端存放低位字節;
?
1.3.3.3.標準屬性
Compatible(兼容)
屬性名稱 | 兼容值 |
---|---|
值類型 |
|
描述 | 兼容屬性值由定義設備特定編程模型的一個或多個字符串組成。客戶端程序應使用此字符串列表選擇設備驅動程序。該屬性值包含一個從最特定到最通用的null終止字符串的串聯列表。它們允許設備表達其與一系列類似設備的兼容性,可能允許單個設備驅動器與幾個設備匹配。推薦的格式是“制造商,型號”,其中制造商是描述制造商名稱的字符串(如股票代號)。 |
示例:
compatible?=“fsl,mpc8641”,“ns16550”;
?
在此示例中,操作系統將首先嘗試查找支持fsl,mpc8641-uartmpc8641的設備驅動程序。如果找不到驅動程序,然后,它將嘗試定位受支持的更通用的ns16550設備類型驅動程序 。
一般驅動程序文件都會有個OF匹配表,此匹配表保存著一些compatible值,如果設備節點的 compatible屬性值和OF匹配表中的任何一個值相等,那么就表示設備可以使用這驅動。比如在文件drvier/misc/memctrl.c中:
?
static?struct?of_device_id_xxx_memctrl_of_match[]?=?{ ?????{?.compatible?=?"xxxx,memctrl",?}, ?????{}, };
?
對應的,在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(型號)
屬性名稱 | 模型值 |
---|---|
值類型 | ? |
描述 | 指定設備的制造商型號。推薦的格式為:“制造商,型號”,其中制造商是描述制造商名稱的字符串(如股票代號)。 |
示例:
?
model =“fsl,MPC8349EMITX”;
?
Phandle(pointer handle)
屬性名稱 | pointer handle值 |
---|---|
值類型 | ? |
描述 | phandle屬性指定設備樹中唯一節點的數字標識符。phandle屬性值被需要引用與該屬性關聯的節點的其他節點使用。 |
示例:
?
pic@10000000?{ phandle?=?<1>; interrupt-controller; };
?
定義了1的phandle值。另一個設備節點可以引用phandle值為1的pic節點:
?
another-device-node?{ interrupt-parent?=?<1>; };
?
Status
屬性名稱 | 狀態值 |
---|---|
值類型 |
|
描述 | 狀態屬性指示設備的操作狀態,其有效值如下:"okay":指示設備可運行。"disabled":表明設備目前尚未運行,但未來可能會運行(例如,未插入或關閉某物)。"fail":表示設備無法運行,在設備中檢測到嚴重錯誤,如果不進行維修就不太可能運行"fail-sss":表示設備無法運行,在設備中檢測到嚴重錯誤,如果不進行維修就不太可能運行,sss部分特定于設備,并指示檢測到的錯誤情況。 |
#address-cells and #size-cells
屬性名稱 | #address-cells,#size-cells |
---|---|
值類型 |
|
描述 | #address-cells和#size-cells屬性可用于設備樹層次結構中包含子節點并描述如何解決子設備節點的任何設備節點。#address-cells屬性定義用于編碼子節點的reg屬性中地址字段的個單元格的數量。#size-cells屬性定義用于編碼子節點的reg屬性中大小字段的個單元格的數量。 |
?
#address-cells?=?<1>; ??#size-cells?=?<0>;
?
表示reg屬性中有一個u32表示address,沒有表示reg大小的數據,所以:reg = <0x0>; 即reg的起始地址為0x0,不描述其大小
?
#address-cells?=?<1>; ???#size-cells?=?<1>;
?
表示reg屬性中有一個u32表示address,有一個u32表示size,所以:reg = <0x00000000 0x00040000>; 即reg的起始地址為0x00000000,大小是0x00040000
Reg
屬性名稱 | reg |
---|---|
值類型 |
|
描述 | reg屬性描述設備資源在其父總線定義的地址空間內的地址。這通常意味著內存映射IO寄存器塊的偏移和長度,但在某些總線類型上可能有不同的含義。根節點定義的地址空間中的地址為cpu真實地址。該值是一個,由任意數量的地址和長度對組成,<地址長度>。指定地址和長度所需的單元格的數量是總線特定的,由設備節點父級中的#address-cells和#size-cells屬性指定。如果父節點為#size-cells單元格指定值0,則應忽略reg值中的長度字段。 |
示例:假設系統芯片中的設備包含兩個寄存器塊,SOC中偏移0x3000的32字節塊和偏移0xFE00的256字節塊。reg屬性的編碼如下(假設#address-cells和#size-cells值為1):
?
reg=<0x3000?0x20?0xFE00?0x100>;
?
virtual-reg
屬性名稱 | virtual-reg |
---|---|
值類型 |
|
描述 | virtual-reg屬性指定一個有效地址,該地址映射到設備節點的reg屬性中指定的第一個物理地址。此屬性使引導程序能夠為客戶端程序提供已設置的虛擬到物理映射。 |
Ranges
屬性名稱 | range |
---|---|
值類型 |
|
描述 | range屬性提供了一種在總線地址空間(子地址空間)和總線節點父地址空間(父地址空間)之間定義映射或轉換的方法。range屬性值的格式是任意數量的三聯體(子總線地址、父總線地址、長度):1.子總線地址是子總線地址空間內的物理地址。表示地址的單元格數取決于總線,可以通過此節點(出現range屬性的節點)的#address-cells確定。2. 父總線地址是父總線地址空間中的物理地址。表示父地址的單元格數取決于總線,可以通過定義父地址空間的節點的#address-cells屬性確定。3. 長度指定子地址空間中范圍的大小。表示大小的單元格數可以根據該節點(出現range屬性的節點)的#size-cells確定。如果屬性用值定義,則它指定父地址和子地址空間相同,并且不需要地址轉換。如果總線節點中不存在該屬性,則假設節點的子節點和父地址空間之間不存在映射。 |
示例:
?
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節點指定了<0x0 0xe0000000 0x00100000>;此屬性值指定對于1024KB范圍的地址空間,在物理0x0處尋址的子節點映射到物理0xe0000000的父地址。通過這種映射,串行設備節點可以通過0xe0004600地址的加載或存儲、0x4600(在注冊表中指定)的偏移量以及范圍中指定的0xe0000000映射尋址。
dma-ranges
屬性名稱 | dma-ranges |
---|---|
值類型 |
|
描述 | dma-range屬性用于描述存儲器映射總線的直接存儲器訪問(dma)結構,其設備樹父級可以從總線的dma操作訪問。它提供了一種定義總線物理地址空間與總線父級物理地址空間之間映射或轉換的方法。dma-range屬性的值的格式是任意數量的(子總線地址、母線地址、長度)。指定的每個三聯體描述連續DMA地址范圍。1. 子總線地址是子總線地址空間內的物理地址。表示地址的單元格數取決于總線,可以通過該節點(dma-range屬性出現的節點)的#address-cells地址單元格確定。2. 父總線地址是父總線地址空間中的物理地址。表示父地址的單元格數取決于總線,可以通過定義父地址空間的節點的#address-cells屬性確定。3. 長度指定子地址空間中范圍的大小。表示大小的單元格數量可以根據該節點(dma-range屬性出現的節點)的#size-cells確定。 |
Name(已棄用)
屬性名稱 | name |
---|---|
值類型 |
|
描述 | name屬性用于記錄節點名字,name屬性已經被棄用 ,不推薦使用 name屬性,一些老的設備樹文件可能會使用此屬性。 |
device_type
屬性名稱 | device_type |
---|---|
值類型 |
|
描述 | 由于DTS沒有FCode,因此不建議使用該屬性。只能用于在cpu節點和memory節點中,以便與IEEE 1275衍生設備兼容。 |
1.3.4.基本設備節點類型
所有設備樹文件均要包含一個根文件,并且所有設備樹文件均應在根節點下存在以下節點:
1個/cpus節點
至少一個/memory節點
使用說明:R = 必需,O = 可選,OR = 可選但推薦,SD = 參見定義,所有其他的標準屬性均可接受,但可選
1.3.4.1.Root node
devicetree有一個單獨的根節點,所有其他設備節點都是它的后代。根節點的完整路徑為/。
屬性名稱 | 使用說明 | 類型 | 定義 |
---|---|---|---|
#address-cells | R |
|
root子節點的reg property地址格式。 |
#size-cells | R |
|
root子節點的reg property大小格式。 |
model | R |
|
指定唯一標識。系統板型號。推薦格式為“制造商,型號” |
compatible | R |
|
指定平臺體系結構列表。該平臺兼容。這一建議可供操作系統在選擇平臺特定代碼時使用。 |
1.3.4.2./aliases節點
設備樹文件可能具有一個別名節點(/aliases),該節點定義一個或多個別名屬性。別名節點應位于設備樹的根節點,并且具有節點名稱/別名。/aliases節點的每個屬性都定義了一個別名。屬性名稱指定別名。屬性值指定設備樹中節點的完整路徑。例如,屬性serial0 = "/simple-bus@fe000000/serial@llc500"定義了別名serial0。別名的命名規則如下:
字符 | 描述 |
---|---|
0-9 | 數字 |
a-z | 小寫字母 |
- | 破折號 |
1.3.4.3./memory節點
所有設備樹都需要內存設備節點,并描述系統的物理內存布局。如果系統具有多個范圍的內存,則可以創建多個內存節點,或者可以在單個內存節點的reg屬性中指定范圍。
/memory節點的屬性要求如下:
屬性名稱 | 使用說明 | 類型 | 定義 |
---|---|---|---|
device_type | R | O |
|
reg | R |
|
由任意數量的地址和大小對組成,它們指定內存范圍的物理地址和大小 |
initial-mapped-area | O |
|
指定”初始映射區域”的地址和大小,是一個由三元組組成的屬性編碼數組(有效地址、物理地址、大小)。有效和物理地址均應為64位(值),大小應為32位(值) |
在xxx.dts中
?
memory?{ ???????reg?=??<0x40000000?0x10000000>;???起始地址0x40000000?長度0x10000000(32MB) ?};
?
1.3.4.4./chosen 節點
/chosen 節點不代表系統中的實際設備,而是描述了在運行時由系統固件選擇或指定的參數。它應該是根節點的子節點。
屬性名稱 | 使用說明 | 類型 | 定義 |
---|---|---|---|
bootargs | O |
|
指定程序的啟動參數。如果不需要引導參數,則該值可能為空字符串 |
stdout-path | O |
|
指定到表示用于引導控制臺輸出的設備的節點的完整路徑。如果包含’:’,則它將終止路徑。該值可以是別名。 |
stdin-path | O |
|
指定到表示用于引導控制臺輸入的設備的節點的完整路徑。如果包含’:’,則它將終止路徑。該值可以是別名。 |
示例:
?
chosen?{ bootargs?=?"root=/dev/nfs?rw?nfsroot=192.168.1.1?console=ttyS0,115200"; };
?
1.3.4.5./cpus節點屬性
所有設備樹均需要/cpus/cpu節點。它并不代表系統中的真實設備,而是作為代表系統cpu的子cpu節點的容器。
屬性名稱 | 使用說明 | 類型 | 定義 |
---|---|---|---|
device_type | R |
|
值應為“cpu” |
reg | R |
|
它為CPU節點表示的CPU/線程定義了唯一的CPU/線程ID。如果CPU支持多線程,則reg是一個數組,每個線程具有一個元素。 |
clock-frequency | R |
|
以Hz為單位指定CPU的當前時鐘速度,格式可以是,或 |
timebase-frequency | R |
|
指定更新時基的當前頻率 |
status | SD |
|
此屬性應存在于對稱多進程(SMP)CPU的節點中 配置。”okay”:CPU正在運行;”disable”:CPU處于靜止狀態。 |
1.3.5.中斷映射
在設備樹中,存在邏輯中斷樹,該邏輯中斷樹表示平臺硬件中斷的層次結構和路由。在設備樹中,使用interrupt-parent屬性表示中斷源與中斷控制器的物理連線。代表產生中斷的設備節點包含一個中斷父屬性,該屬性具有一個虛擬值,指向給設備的中斷所路由到的設備(通常是中斷控制器)。
如果產生中斷的設備不具有中斷父屬性,則假定其中斷父節點為其設備父節點。每個中斷產生設備都包含一個中斷屬性,該屬性的值描述該設備的一個或多個中斷源。每個源都用稱為中斷描述符表示。中斷描述符的格式和含義是特定于中斷域的,即,取決于中斷域根節點上節點的屬性。中斷域的根使用#interrupt-cells屬性定義對中斷描述符進行編碼所需的值數量。
中斷域是解釋中斷描述符的上下文。中斷域的根可以是中斷控制器(interrupt controller)或中斷連接器(interrupt nexus):
中斷控制器是物理設備,需要一個驅動程序來處理通過它路由的中斷。它還可能級聯到另一個中斷域。中斷控制器由設備樹中該節點上的interrupt-controller指定。
中斷連接器定義了一個中斷域和另一個中斷域之間的轉換。翻譯基于特定領域和總線的信息。使用interrupt-map屬性在域之間進行轉換。例如,PCI控制器設備節點可以是一個中斷連接器,定義從PCI中斷命名空間(INTA、INTB等)到具有中斷請求(IRQ)編號的中斷控制器的轉換。
?
1.3.5.1.Interrupts
屬性名稱 | interrupts |
---|---|
值類型 |
|
描述 | 設備節點的中斷屬性定義設備生成的中斷。interrupts屬性的值由任意數量的中斷描述符組成。中斷描述符的格式由中斷域根定義。 |
示例:
?
interrupts?=?;
?
1.3.5.2.interrupt-parent
屬性名稱 | interrupt-parent |
---|---|
值類型 |
|
描述 | 由于中斷樹中節點的層次結構可能與device tree不匹配,因此interrupt-parent屬性可用于明確中斷父級的定義。該值是中斷父級的phandle。如果設備缺少此屬性,則假定其中斷父級為其設備樹父級。 |
示例:
?
interrupt-parent?=?<&gpe>;
?
1.3.5.3.interrupts-extended
屬性名稱 | interrupts-extended |
---|---|
值類型 |
|
描述 | 擴展的中斷屬性列出了設備產生的中斷。當設備連接到多個中斷控制器時,應該使用interrupts-extended代替interrupts,因為它會在每一個中斷描述符編碼一個父代phandle |
示例:
?
interrupts-extended?=?<&pic?0xA?8>,?<&gic?0xda>;
?
1.3.5.4.#interrupt-cells
屬性名稱 | #interrupt-cells |
---|---|
值類型 |
|
描述 | #interrupt-cells屬性定義對中斷域的中斷描述符進行編碼所需的單元數量 |
1.3.5.5.interrupt-controller
屬性名稱 | interrupt-controller |
---|---|
值類型 |
|
描述 | 中斷控制器屬性的存在將節點定義為中斷控制器節點。 |
1.4.Device Tree binary格式
Devicetree Blob (DTB)格式是Devicetree數據的平面二進制編碼。它用來在軟件程序之間交換設備數據。例如,在引導操作系統時,固件將向操作系統內核傳遞一個DTB。
DTB格式將devicetree數據編碼為一個單一的、線性的、無指針的數據結構。它由一個小標題組成,接下來是三個大小可變的部分:內存保留塊、結構塊和字符串塊這些應該按照這個順序出現在扁平的devicetree中。
因此。當按地址加載到內存中時,設備樹結構作為一個整體。將類似于圖中的圖表。
1.4.1.dt_header
設備樹的頭部是由以下C結構體定義的。所有字段都是32位整數,以big-endian格式存儲。
?
struct?fdt_header?{ 此字段應包含值0xd00dfeed(big-endian) ?uint32_t?magic;????/*?magic?word?FDT_MAGIC?*/ 此字段應包含設備數據結構的總大小(字節)。該大小應包含結構的所有部分:報頭、內存預留塊、結構塊和字符串塊,以及塊之間或最終塊之后的自由空間間隙。 ?uint32_t?totalsize;???/*?total?size?of?DT?block?*/ 此字段應包含結構塊從標題開始的字節偏移 ?uint32_t?off_dt_struct;???/*?offset?to?structure?*/ 此字段應包含從標題開始的字符串塊的字節偏移量 ?uint32_t?off_dt_strings;??/*?offset?to?strings?*/ 此字段應包含從標題開始的內存保留塊的字節偏移量 ?uint32_t?off_mem_rsvmap;??/*?offset?to?memory?reserve?map?*/ 此字段應包含設備數據結構的版本 ?uint32_t?version;???/*?format?version?*/ 此字段應包含設備所用版本向后兼容的最低版本數據結構 ?uint32_t?last_comp_version;??/*?last?compatible?version?*/ ?/*?version?2?fields?below?*/ 此字段應包含系統引導CPU的物理ID。它應與設備樹中CPU節點的reg屬性中給定的物理ID相同 ?uint32_t?boot_cpuid_phys;??/*?Which?physical?CPU?id?we're?booting?on?*/ ?/*?version?3?fields?below?*/ 此字段應包含字符串塊部分的字節長度 ?uint32_t?size_dt_strings;??/*?size?of?the?strings?block?*/ ?/*?version?17?fields?below?*/ 此字段應包含結構塊部分的字節長度 ?uint32_t?size_dt_struct;??/*?size?of?the?structure?block?*/ };
?
1.4.2.memory reservation block
內存保留塊向客戶端程序提供物理內存中被保留的區域的列表,這些內存不用于一般的內存分配,目的是保護重要的數據結構不被客戶端程序覆蓋。這個區域包括了若干的reserve memory描述符。每個reserve memory描述符是由address和size組成。其中address和size都是用U64來描述:
?
struct?fdt_reserve_entry?{ uint64_t?address; uint64_t?size; };
?
1.4.3.Structure block
結構塊描述了設備樹本身的結構和內容。它由若干的分片組成,每個分片開始位置都是保存了令牌(token),以此來描述該分片的屬性和內容。
FDT_BEGIN_NODE (0x00000001):該token描述了一個node的開始位置,緊挨著該token的就是node name(包括unit address)
FDT_END_NODE (0x00000002):該token描述了一個node的結束位置
FDT_PROP (0x00000003):該token描述了一個property的開始位置,該token之后是兩個u32的數據。它們之后就是長度為len的具體的屬性值數據。
?
struct?{ uint32_t?len;?表示該property value data的size。 uint32_t?nameoff;?表示該屬性字符串在device?tree?strings?block的偏移值 }
?
FDT_NOP (0x00000004):被解析設備樹的程序忽略,可用于覆蓋其他屬性,以刪除它
FDT_END (0x00000009):標記結構塊的結束所以,一個DTB的結構塊可能如下:
?
(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
定義了各個node中使用的屬性的字符串表。由于很多屬性會出現在多個node中,因此,所有的屬性字符串組成了一個string block。這樣可以壓縮DTB的size。
1.5.Linux解析設備樹
設備樹描述了設備的詳細信息,這些信息包括數字類型的、字符串類型的、數組類型的,我們在編寫驅動時需要去獲取這些信息。Linux內核提供一系列以of_開頭的函數來獲取設備樹信息,這些函數的原型都定義在include/linux/of.h中。設備以節點的形式掛在設備樹上,Linux內核使用device_node結構體來描述一個節點,其定義在include/linux/of.h中:
?
struct?device_node?{ ?const?char?*name;?????device?node?name ?const?char?*type;?????對應device_type的屬性 ?phandle?phandle;??????對應該節點的phandle屬性 ?const?char?*full_name;??從“/”開始的,表示該node的full?path ?Struct??property?*properties;??????該節點的屬性列表 如果需要刪除某些屬性,kernel并非真的刪除,而是掛入到deadprops的列表 ?struct??property?*deadprops;?/*?removed?properties?*/ parent、child以及sibling將所有的device?node連接起來 ?Struct??device_node?*parent;?? ?Struct??device_node?*child; ?Struct??device_node?*sibling; 通過該指針可以獲取相同類型的下一個node ?Struct??device_node?*next;?/*?next?device?of?same?type?*/ 通過該指針可以獲取node?global?list下一個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.查找節點的 OF函數
1.5.1.1.of_find_node_by_name
功能:Find a node by its "name" property函數
?
struct?device_node?*of_find_node_by_name(struct?device_node?*from, ?const?char?*name)
?
參數:
?
@from:開始查找的節點,如果為NULL表示從根節點開始查找整個設備樹。 @name::要查找的節點名字。
?
返回值:找到的節點,如果為NULL表示查找失敗。
1.5.1.2.of_find_node_by_path
功能:Find a node matching a full OF path函數:
?
struct?device_node?*of_find_node_by_path(const?char?*path)
?
參數:@path:完整的匹配路徑返回值:找到的節點,如果為NULL表示查找失敗。
1.5.1.3.of_find_node_by_type
功能Find a node by its "device_type" property函數
?
struct?device_node?*of_find_node_by_type(struct?device_node?*from, ?const?char?*type)
?
參數
?
@from:開始查找的節點,如果為NULL表示從根節點開始查找整個設備樹 @type:?要查找的節點類型
?
返回值找到的節點,如果為NULL表示查找失敗。
1.5.1.4.of_find_compatible_node
功能通過device_type和compatible查找指定節點函數
?
struct?device_node?*of_find_compatible_node(struct?device_node?*from,const?char?*type,?const?char?*compatible)
?
參數
?
@from:開始查找的節點,如果為NULL表示從根節點開始查找整個設備樹 @type:?要查找的節點device_type屬性 @compatible:節點的compatible屬性列表
?
返回值找到的節點,如果為NULL表示查找失敗。
1.5.1.5.of_find_node_with_property
功能通過屬性名查找指定節點函數
?
struct?device_node?*of_find_node_with_property(struct?device_node?*from,const?char?*prop_name)
?
參數
?
@from:開始查找的節點,如果為NULL表示從根節點開始查找整個設備樹 @type:?要查找的節點屬性名稱
?
返回值找到的節點,如果為NULL表示查找失敗。
1.5.2.查找父 /子節點的 OF函數
1.5.2.1.of_get_parent
功能函數用于獲取指定節點的父節點(如果有父節點的話 )函數
?
struct?device_node?*of_get_parent(const?struct?device_node?*node)
?
參數
?
@node:要查找父節點的節點
?
返回值找到的父節點
1.5.2.2.of_get_next_available_child
功能獲取子節點,并跳過status = "disabled"的節點函數
?
struct?device_node?*of_get_next_available_child(const?struct?device_node?*node,struct?device_node?*prev)
?
參數
?
@node:?父節點 @prev:當前父節點的上一個子節點,?如果為空,則獲取第一個子節點
?
返回值找到的子節點
1.5.3.提取屬性值的 OF函數
Linux內核使用struct property來保存節點的屬性,其定義在/include/linux/of.h中:
?
struct?property?{ ?char??*name;??????屬性的名稱 ?int??length;??????屬性的長度 ?void??*value;?????屬性的值 ?struct?property?*next;???下一個屬性 ?unsigned?long?_flags;????? ?unsigned?int?unique_id; ?struct?bin_attribute?attr; };
?
1.5.3.1.of_find_property
功能尋找指定的屬性函數
?
struct?property?*of_find_property(const?struct?device_node?*np, ??????const?char?*name, ??????int?*lenp)
?
參數
?
@np:?設備節點 @name:屬性名稱 @lenp:屬性的字節數
?
返回值找到的屬性
1.5.3.2.讀取屬性中u8、u16、u32和u64類型的數組數據
當設置sz為1時,就是讀取一個數據,Linux內核也是這么封裝的。
?
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
功能找到并讀取屬性字符串函數
?
int?of_property_read_string(struct?device_node?*np,?const?char?*propname,const?char?**out_string)
?
參數
?
@np:?設備節點 @propname:屬性名稱 @out_string:讀取的字符串
?
返回值
?
0:讀取成功 -EINVAL:屬性不存在 -ENODATA:屬性沒有這個值 -EILSEQ:字符串不是以空字符’0’結尾
?
2.設備樹解析流程
2.1.內核啟動并獲取設備樹
在uboot引導內核的時候,會將設備樹在物理內存中的物理起始內存地址傳遞給Linux內核,然后Linux內核在unflattern_device_tree中解析設備鏡像,并利用掃描到的信息創建由device node構成的鏈表,全局變量of_allnodes指向鏈表的根節點,設備樹的每一個節點都由一個struct device_node與之對應。unflatten_device_tree的意思是解開設備樹,在這個函數里調用了__unflatten_device_tree這一函數:
?
/** ?*?__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))
?
所以,現在為止,我們得到了一個名為of_allnodes的struct *device_node,它指向了設備樹展開后的device_node樹,后續的操作都是基于device_node樹。
2.2.創建platform_device
內核從啟動到創建設備的過程大致如下:在do_initcalls中會傳遞level給do_initcall_level來調用不同層次的初始化函數,level的對應關系見linux-3.10/include/linux/init.h 第196行。在這個初始化過程中,會調用一個customize_machine的函數。
2.3.Platform driver注冊流程
此節分析Platform driver的注冊流程,以memctrl驅動的注冊為例分析。關于系統調用驅動初始化函數的流程分析,參考自動初始化機制章節。本章節分析從設備驅動文件的xxx_init函數開始分析。
2.3.1.struct platform_driver
platform_driver是在device_driver之上的一層封裝,其結構如下:
?
struct?platform_driver?{ ?int?(*probe)(struct?platform_device?*);???探測函數 ?int?(*remove)(struct?platform_device?*);??驅動卸載時執行 ?void?(*shutdown)(struct?platform_device?*);??關機時執行函數 ?int?(*suspend)(struct?platform_device?*,?pm_message_t?state);??掛起函數 ?int?(*resume)(struct?platform_device?*);?????恢復函數 ?struct?device_driver?driver;???????????管理的driver對象 ?const?struct?platform_device_id?*id_table;???匹配時使用 };
?
2.3.2.struct device_driver
struct device_driver是系統提供的基本驅動結構:
?
struct?device_driver?{ ?const?char???*name;??驅動名稱 ?struct?bus_type???*bus;?所屬總線 ?struct?module???*owner;?模塊擁有者 ?const?char???*mod_name;?內建的模塊使用 ?bool?suppress_bind_attrs;??是否綁定到sysfs ?const?struct?of_device_id??*of_match_table;?設備樹匹配表 ?const?struct?acpi_device_id??*acpi_match_table;?ACPI匹配表 ?int?(*probe)?(struct?device?*dev);??探測設備 ?int?(*remove)?(struct?device?*dev);?與設備脫離時調用 ?void?(*shutdown)?(struct?device?*dev);?在關機時關閉設備 ?int?(*suspend)?(struct?device?*dev,?pm_message_t?state);?使設備進入睡眠模式調用 ?int?(*resume)?(struct?device?*dev);??喚醒設備時調用 ?const?struct?attribute_group?**groups;?自動創建的默認屬性組 ?const?struct?dev_pm_ops?*pm;??設備的功耗管理 ?struct?driver_private?*p;?驅動的私有數據 };
?
2.3.3.platform_driver_register
Platform_driver的注冊接口是platform_driver_register,其定義如下:
?
int?platform_driver_register(struct?platform_driver?*drv) { ?drv->driver.bus?=?&platform_bus_type;??設置總線類型 ?if?(drv->probe)????確認定義了probe函數????? ??drv->driver.probe?=?platform_drv_probe;??里面實際調用的是drv的probe函數 ?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接口是為注冊總線驅動做一些準備工作,定義了總線類型,設置了driver的部分接口,最后driver_register會向總線注冊驅動
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);?檢查驅動是否已經注冊 ?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添加驅動屬性 ?if?(ret)?{ ??bus_remove_driver(drv); ??return?ret; ?} ?kobject_uevent(&drv->p->kobj,?KOBJ_ADD);???涉及到uevent,暫時不分析 ?return?ret; }
?
2.3.5.bus_add_driver
由以上分析可知,驅動的注冊,重點在bus_add_driver()函數,它會向總線添加驅動:
?
Drivers/base/bus.c int?bus_add_driver(struct?device_driver?*drv) { ?struct?bus_type?*bus; ?struct?driver_private?*priv;??包含與驅動相關的kobject和klist結構 ?int?error?=?0; ?bus?=?bus_get(drv->bus);??獲取設備所屬的總線類型 ?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)?{?如果設置了自動探測 ??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會嘗試綁定設備和驅動。編譯總線上的所有設備,然驅動挨個嘗試匹配,如果driver_probe_device()返回0且@dev->driver被設置,就代表找到了一對兼容的設備驅動。
?
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
對于每一個總線的設備,driver_attach都會調用__driver_attach來嘗試與驅動匹配。
?
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))??匹配設備和驅動,這里調用的是platform_match ??return?0; ?if?(dev->parent)?/*?Needed?for?USB?*/ ??device_lock(dev->parent); ?device_lock(dev);??設置互斥鎖,防止其他進程訪問設備資源 ?if?(!dev->driver)?? 如果設備沒有驅動,則為設備探測驅動,這個函數與注冊設備調用的是同一個函數 ??driver_probe_device(drv,?dev);?? ?device_unlock(dev); ?if?(dev->parent) ??device_unlock(dev->parent); ?return?0; }
?
driver_probe_device里調用really_probe函數,并在really_probe中調用驅動文件中的probe函數,對于memctrl驅動而言,就是xxxx_memctrl_probe函數。至此,platfprm driver就注冊好了。
2.4.Platform Bus的匹配原則
由以上的代碼分析得知,注冊platform device時,會調用__device_attach -> driver_match_device,注冊platform driver時,會調用__driver_attach -> driver_match_device,也就是說設備和驅動都會調用到這個函數:
?
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,這是驅動綁定的總線提供的匹配函數,這里注冊的是platform總線設備,而platform總線的定義參考3.2.6 platform_bus_type。Platform對應的match函數為: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
根據驅動的of_match_table判斷是否有驅動與之匹配。對memctrl驅動而言,其of_match_table如下:
?
static?struct?of_device_id?xxxx_memctrl_of_match[]?=?{ ?{?.compatible?=?"xxxx,memctrl",?}, ???{}, };
?
of_driver_match_device的執行流程如下:
所以重點應該在__of_match_node函數:
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])??查找屬性,檢測節點的compatible是否與驅動的一致 ???????match?&=?__of_device_is_compatible(node,?matches->compatible); ??if?(match) ???????return?matches; ??????matches++; ?} ?return?NULL; }
?
3.使用設備資源
4.自動初始化機制
4.1.編譯到內核
4.1.1.module_init宏展開
Linux中每一個模塊都有一個module_init函數,并且有且只有一個,其定義如下:
?
/** ?*?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現在按照功能分組到單獨的子部分。子部分內部的順序由鏈接順序決定。為了向后兼容,initcall()將調用放到device init小節中。需要定義initcall()的’id’參數,以便多個initcall可以指向同一個處理程序,而不會導致重復符號構建錯誤。若不理解上述代碼的用法,可以參考__attribute__的section用法和C語言宏定義中#和##的用法。所以將__define_initcall展開將會是下面的內容:
?
假設__define_initcall(led_init,?6) Static?initcall_t?__initcall_led_init6?__used? __attribute__((__section__(".initcall6.init")))?=?led_init
?
即是定義了一個類型為initcall_t的函數指針變量__initcall_led_init6,并賦值為led_init,該變量在鏈接時會鏈接到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)會展開為:
?
__initcall6_start?=?.;??*(.initcall6.init)???*(.initcall6s.init)
?
所以__initcall_led_init6會鏈接到
?
section(.initcall6.init)
?
4.1.3.初始化
內核啟動流程為:
?
do_initcall_level的主要內容如下:
?
/*?linux3.10/init/main.c?line?744?*/ static?void?__init?do_initcall_level(int?level) { ..... ?for?(fn?=?initcall_levels[level];?fn??
由代碼可知,內核會依次調用level段存儲的初始化函數。比如對于模塊來說level等于6。
4.2.動態加載的模塊(.ko)
4.2.1.Module_init展開
如果設置為編譯成動態加載的模塊(.ko),module_init的展開形式與編譯到內核不一樣。
?
/*?Each?module?must?use?one?module_init().?*/ #define?module_init(initfn)????? ?static?inline?initcall_t?__inittest(void)?????檢查定義的函數是否符合initcall_t類型 ?{?return?initfn;?}????? ?int?init_module(void)?__attribute__((alias(#initfn)));?
alias屬性是GCC的特有屬性,將定義init_module為函數initfn的別名,所以module_init(initfn)的作用就是定義一個變量名init_module,其地址和initfn是一樣的。
4.2.2.*mod.c文件
編譯成module的模塊都會自動產生一個*.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, };?
即定義了一個類型為module的全局變量__this_module,其成員.init就是上文由module_init定義的init_module變量。并且__this_module會被鏈接到section(".gnu.linkonce.this_module")。
4.2.3.動態加載
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)該系統調用對應內核層的sys_init_module函數?
路徑:kernel/module.c
?
SYSCALL_DEFINE3(init_module,…) //加載模塊的ko文件,并解釋各個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數據結構 mod?=?(void?*)sechdrs[modindex].sh_addr; if?(mod->init?!=?NULL) ret?=?do_one_initcall(mod->init);?//調用initfn.?
4.3.__attribute__的section用法
__define_initcall使用了gcc的__attribute__眾多屬性中的section子項,其使用方式為:
?
__attribute__((__section__("section_name")))?
其作用是將作用的函數或數據放入指定的名為”section_name”的段。
4.4. C語言宏定義中#和##的用法
4.4.1.一般用法
我們使用#把宏參數變為一個字符串。
?
#define?PRINT(FORMAT,VALUE) printf("The?value?of"#VALUE"is?"?FORMAT" ",VALUE)??
調用:printf("%d",x+3); ? ? --> ? ? 打印:The value of x+3 is 20
這是因為”The value of”#VALUE”is ” FORMAT” ”實際上是包含了”The value of “,#VALUE,”is “,FORMAT,” ” 五部分字符串,其中VALUE和FORMAT被宏參數的實際值替換了。
用##把兩個宏參數貼合在一起
?
#define?ADD_TO_SUM(sum_number,val)?sum##sum_bumber+=(val)?
調用:ADD_TO_SUM(2,100); ? ? --> ? ? 打印:sum2+=(100)
需要注意的是凡宏定義里有用'#'或'##'的地方宏參數是不會再展開。
4.4.2.'#'和'##'的一些應用特例
合并匿名變量名
?
#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表示該行行號;第一層:ANONYMOUS(static int); --> ?__ANONYMOUS0(static int, LINE);第二層:? ? ? ? ? ? ? ? ? ? ? ? ? ?--> ?___ANONYMOUS1(static int, _anonymous, 70);第三層:? ? ? ? ? ? ? ? ? ? ? ? ? ?--> ?static int ?_anonymous70;即每次只能解開當前層的宏,所以__LINE__在第二層才能被解開;
填充結構
?
#define??FILL(a)???{a,?#a}? enum?IDD{OPEN,?CLOSE};? typedef?struct?MSG{? ??IDD?id;? ??const?char??msg;? }MSG;? MSG?_msg[]?=?{FILL(OPEN),?FILL(CLOSE)};??
相當于:
?
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__);??
得到一個數值類型所對應的字符串緩沖大小
?
#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];??
這里相當于:
?
char??buf[11];- END -?
?
審核編輯:劉清
評論
查看更多