真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

成都創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),赤壁企業(yè)網(wǎng)站建設(shè),赤壁品牌網(wǎng)站建設(shè),網(wǎng)站定制,赤壁網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,赤壁網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

1. 設(shè)備樹概念

1.1.設(shè)備樹感性認(rèn)識(shí)

設(shè)備樹(Device Tree),將這個(gè)詞分開(kāi)就是“設(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)。

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

1.2.DTS、DTB、DTC

DTS:設(shè)備樹源碼文件;DTB:將DTS編譯后得到的二進(jìn)制文件;DTC:DTS的編譯工具,其源碼在內(nèi)核的scripts\dtc目錄下?;谕瑯觓rm架構(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的開(kāi)發(fā)板,只要設(shè)置CONFIG_ARCH_xxx=y,所有用到這顆SOC的DTS都會(huì)編譯成DTB。如果后續(xù)還用到了這顆SOC設(shè)計(jì)的開(kāi)發(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ì)被展開(kāi),展開(kāi)的位置在/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è)字符,可以由以下的字符組成:

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

表 2-1節(jié)點(diǎn)名稱的有效字符

節(jié)點(diǎn)名稱應(yīng)以較低或大寫字符開(kāi)頭,并應(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ū)分開(kāi)來(lái)。對(duì)于reg格式和單位地址,特定總線的綁定可能會(huì)指定附加更具體的要求。根節(jié)點(diǎn)沒(méi)有節(jié)點(diǎn)名稱或單位地址。它由正斜杠(/)標(biāo)識(shí)。

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

圖 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è)字符,可以由以下的字符組成:

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

非標(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ù)組。

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

big-endian和little-endian(大小端):big-endian:是指低地址端存放高位字節(jié);little-endian:是指高地址端存放低位字節(jié);

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

1.3.3.3. 標(biāo)準(zhǔn)屬性

1.Compatible(兼容)

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

示例:

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>; };

2.Model(型號(hào))

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

示例:

model =“fsl,MPC8349EMITX”;

3.Phandle(pointer handle)

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

示例:

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>; };

4.Status

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

5.#address-cells and #size-cells

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

#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

6.Reg

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒ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>;

7.virtual-reg

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

8.Ranges

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

示例:

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映射尋址。

9.dma-ranges

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

10.Name(已棄用)

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

11.device_type

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

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 = 參見(jiàn)定義,所有其他的標(biāo)準(zhǔn)屬性均可接受,但可選

1.3.4.1. Root node

devicetree有一個(gè)單獨(dú)的根節(jié)點(diǎn),所有其他設(shè)備節(jié)點(diǎn)都是它的后代。根節(jié)點(diǎn)的完整路徑為/。

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

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ī)則如下:

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

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)的屬性要求如下:

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

在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)。

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

示例:

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)的容器。

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

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)換。

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

1.3.5.1. Interrupts

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

示例:

interrupts = ;

1.3.5.2. interrupt-parent

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

示例:

interrupt-parent = <&gpe>;

1.3.5.3. interrupts-extended

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

示例:

interrupts-extended = <&pic 0xA 8>, <&gic 0xda>;

1.3.5.4. #interrupt-cells

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

1.3.5.5. interrupt-controller

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型

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)題開(kāi)始的字節(jié)偏移  uint32_t off_dt_struct;   /* offset to structure */ 此字段應(yīng)包含從標(biāo)題開(kāi)始的字符串塊的字節(jié)偏移量  uint32_t off_dt_strings;  /* offset to strings */ 此字段應(yīng)包含從標(biāo)題開(kāi)始的內(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è)分片開(kāi)始位置都是保存了令牌(token),以此來(lái)描述該分片的屬性和內(nèi)容。

  • FDT_BEGIN_NODE (0x00000001):該token描述了一個(gè)node的開(kāi)始位置,緊挨著該token的就是node name(包括unit  address)

  • FDT_END_NODE (0x00000002):該token描述了一個(gè)node的結(jié)束位置

  • FDT_PROP  (0x00000003):該token描述了一個(gè)property的開(kāi)始位置,該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_開(kāi)頭的函數(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;  從“/”開(kāi)始的,表示該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:開(kāi)始查找的節(jié)點(diǎn),如果為NULL表示從根節(jié)點(diǎn)開(kāi)始查找整個(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:開(kāi)始查找的節(jié)點(diǎn),如果為NULL表示從根節(jié)點(diǎn)開(kāi)始查找整個(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:開(kāi)始查找的節(jié)點(diǎn),如果為NULL表示從根節(jié)點(diǎn)開(kāi)始查找整個(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:開(kāi)始查找的節(jié)點(diǎn),如果為NULL表示從根節(jié)點(diǎn)開(kāi)始查找整個(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的意思是解開(kāi)設(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è)備樹展開(kāi)后的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)系見(jià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ù)開(kāi)始分析。

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\n", 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...\n", 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\n", 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\n",    __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\n",    __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\n",     __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宏展開(kāi)

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展開(kāi)將會(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ì)展開(kāi)為:

__initcall6_start = .;  *(.initcall6.init)   *(.initcall6s.init)

所以__initcall_led_init6會(huì)鏈接到

section(.initcall6.init)

4.1.3. 初始化

內(nèi)核啟動(dòng)流程為:

如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒ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 < initcall_levels[level+1]; fn++)           do_one_initcall(*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展開(kāi)

如果設(shè)置為編譯成動(dòng)態(tài)加載的模塊(.ko),module_init的展開(kāi)形式與編譯到內(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"\n",VALUE)

調(diào)用:printf("%d",x+3); --> 打?。篢he value of x+3 is 20

這是因?yàn)椤盩he value of”#VALUE”is ” FORMAT”\n”實(shí)際上是包含了”The value of “,#VALUE,”is  “,FORMAT,”\n” 五部分字符串,其中VALUE和FORMAT被宏參數(shù)的實(shí)際值替換了。

用##把兩個(gè)宏參數(shù)貼合在一起

#define ADD_TO_SUM(sum_number,val) sum##sum_bumber+=(val)

調(diào)用:ADD_TO_SUM(2,100); --> 打?。簊um2+=(100)

需要注意的是凡宏定義里有用'#'或'##'的地方宏參數(shù)是不會(huì)再展開(kāi)。

4.4.2. '#'和'##'的一些應(yīng)用特例

1.合并匿名變量名

#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; 即每次只能解開(kāi)當(dāng)前層的宏,所以__LINE__在第二層才能被解開(kāi);

2.填充結(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}};

3.記錄文件名

#define  _GET_FILE_NAME(f)   #f  #define  GET_FILE_NAME(f)    _GET_FILE_NAME(f)  static char  FILE_NAME[] = GET_FILE_NAME(__FILE__);

4.得到一個(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];

上述就是小編為大家分享的如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


網(wǎng)站題目:如何讓Linux驅(qū)動(dòng)11-Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型
文章路徑:http://weahome.cn/article/jpdiih.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部