您好,登錄后才能下訂單哦!
這期內(nèi)容當中小編將會給大家?guī)碛嘘P(guān)如何讓Linux驅(qū)動11-Linux設(shè)備驅(qū)動統(tǒng)一模型,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
設(shè)備樹(Device Tree),將這個詞分開就是“設(shè)備”和“樹”,描述設(shè)備樹的文件叫做DTS(Device Tree Source),這個DTS 文件采用樹形結(jié)構(gòu)描述板級設(shè)備,比如CPU 數(shù)量、 內(nèi)存基地址、IIC 接口上接了哪些設(shè)備、SPI 接口上接了哪些設(shè)備等等。設(shè)備樹是樹形數(shù)據(jù)結(jié)構(gòu),具有描述系統(tǒng)中設(shè)備的節(jié)點。每個節(jié)點都有描述所代表設(shè)備特征的鍵值對。每個節(jié)點只有一個父節(jié)點,而根節(jié)點則沒有父節(jié)點。
DTS:設(shè)備樹源碼文件;DTB:將DTS編譯后得到的二進制文件;DTC:DTS的編譯工具,其源碼在內(nèi)核的scripts\dtc目錄下?;谕瑯觓rm架構(gòu)的CPU有很多,同一個CPU會制作很多配置不一的板子,如何正確的編譯所選的板子的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都會編譯成DTB。如果后續(xù)還用到了這顆SOC設(shè)計的開發(fā)板,只要新建一個DTS文件,并將對應(yīng)名稱的DTB文件名加到dtb-$(CONFIG_ARCH_xxx)中,在編譯設(shè)備樹時就會將DTS編譯為二進制的DTB文件。
以下語法分析均以xxx.dts為例。
設(shè)備樹的頭文件擴展名為 .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都有自己的根節(jié)點,但是一個設(shè)備樹文件只允許有一個根節(jié)點,DTC如何處理?將根節(jié)點合并,保留最后一級的根節(jié)點。包含的頭文件內(nèi)容會被展開,展開的位置在/memory和/cpus之間。(存疑,只用xxx.dts編譯過) Q2:如果包含過程中有重復(fù)的compatible,DTC怎么處理?編譯時不會報錯,會生成兩個compatible屬性一樣的節(jié)點。
設(shè)備樹中的每一個節(jié)點都按照以下格式命名:
node-name@unit-address
node-name表示節(jié)點名稱,它的長度范圍應(yīng)該是1~31個字符,可以由以下的字符組成:
表 2-1節(jié)點名稱的有效字符
節(jié)點名稱應(yīng)以較低或大寫字符開頭,并應(yīng)描述設(shè)備的一般類別。節(jié)點的單位地址特定于節(jié)點所在的總線類型。它由表2-1中字符集中的一個或多個ASCII字符組成。單位地址必須與節(jié)點的reg屬性中指定的第一個地址匹配。如果節(jié)點沒有reg屬性,則必須省略@unit-address,并且單獨使用節(jié)點名稱將節(jié)點與樹中相同級別的其他節(jié)點區(qū)分開來。對于reg格式和單位地址,特定總線的綁定可能會指定附加更具體的要求。根節(jié)點沒有節(jié)點名稱或單位地址。它由正斜杠(/)標識。
圖 2-1節(jié)點名稱示例
在圖2-1中,節(jié)點名稱為cpu的兩個節(jié)點通過uint-address 0和1區(qū)分;節(jié)點名稱為ethernet的兩個節(jié)點通過uint-address fe002000和fe003000區(qū)分。在設(shè)備樹中經(jīng)常會看到以下設(shè)備名稱:
watchdog: watchdog@04009800
冒號前的是節(jié)點標簽(label),冒號后是節(jié)點名稱。引入label的目的是方便訪問節(jié)點,可以直接通過&label來訪問這個節(jié)點。比如上述節(jié)點就可以使用&watchdog來訪問。
節(jié)點的名稱應(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
通過指定從根節(jié)點到所需節(jié)點的完整路徑(通過所有子節(jié)點),可以唯一識別devicetree中的節(jié)點。指定設(shè)備路徑的約定是:
/node-name-1/node-name-2/.../node-name-N
例如,在圖2-1中,到cpu#1的設(shè)備路徑為:
/cpus/cpu@1
/為根節(jié)點,在保證完整路徑明確的前提下,可以省略uint-address。
設(shè)備樹中的每個節(jié)點都有描述節(jié)點特性的屬性。屬性由名稱和值組成。
屬性名稱的長度范圍應(yīng)該是1~31個字符,可以由以下的字符組成:
非標準屬性名稱應(yīng)指定唯一的字符串前綴,例如股票代號,用于標識定義該屬性的公司或組織的名稱。示例:
xxx,pin-function = <6>; fsl,channel-fifo-len linux,network-index ibm,ppc-interrupt-server#s
屬性值是包含與屬性關(guān)聯(lián)的信息的零或多個字節(jié)的數(shù)組。
big-endian和little-endian(大小端):big-endian:是指低地址端存放高位字節(jié);little-endian:是指高地址端存放低位字節(jié);
1.Compatible(兼容)
示例:
compatible =“fsl,mpc8641”,“ns16550”;
在此示例中,操作系統(tǒng)將首先嘗試查找支持fsl,mpc8641-uartmpc8641的設(shè)備驅(qū)動程序。如果找不到驅(qū)動程序,然后,它將嘗試定位受支持的更通用的ns16550設(shè)備類型驅(qū)動程序 。
一般驅(qū)動程序文件都會有個OF匹配表,此匹配表保存著一些compatible值,如果設(shè)備節(jié)點的 compatible屬性值和OF匹配表中的任何一個值相等,那么就表示設(shè)備可以使用這驅(qū)動。比如在文件drvier/misc/memctrl.c中:
static struct of_device_id_xxx_memctrl_of_match[] = { { .compatible = "xxxx,memctrl", }, {}, };
對應(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 = <GIC_SPI INT_SDRAM IRQ_TYPE_LEVEL_HIGH>; interrupt-controller; #interrupt-cells = <1>; };
2.Model(型號)
示例:
model =“fsl,MPC8349EMITX”;
3.Phandle(pointer handle)
示例:
pic@10000000 { phandle = <1>; interrupt-controller; };
定義了1的phandle值。另一個設(shè)備節(jié)點可以引用phandle值為1的pic節(jié)點:
another-device-node { interrupt-parent = <1>; };
4.Status
5.#address-cells and #size-cells
#address-cells = <1>; #size-cells = <0>;
表示reg屬性中有一個u32表示address,沒有表示reg大小的數(shù)據(jù),所以:reg = <0x0>; 即reg的起始地址為0x0,不描述其大小
#address-cells = <1>; #size-cells = <1>;
表示reg屬性中有一個u32表示address,有一個u32表示size,所以:reg = <0x00000000 0x00040000>; 即reg的起始地址為0x00000000,大小是0x00040000
6.Reg
示例:假設(shè)系統(tǒng)芯片中的設(shè)備包含兩個寄存器塊,SOC中偏移0x3000的32字節(jié)塊和偏移0xFE00的256字節(jié)塊。reg屬性的編碼如下(假設(shè)#address-cells和#size-cells值為1):
reg=<0x3000 0x20 0xFE00 0x100>;
7.virtual-reg
8.Ranges
示例:
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é)點指定了<0x0 0xe0000000 0x00100000>;此屬性值指定對于1024KB范圍的地址空間,在物理0x0處尋址的子節(jié)點映射到物理0xe0000000的父地址。通過這種映射,串行設(shè)備節(jié)點可以通過0xe0004600地址的加載或存儲、0x4600(在注冊表中指定)的偏移量以及范圍中指定的0xe0000000映射尋址。
9.dma-ranges
10.Name(已棄用)
11.device_type
所有設(shè)備樹文件均要包含一個根文件,并且所有設(shè)備樹文件均應(yīng)在根節(jié)點下存在以下節(jié)點:
1個/cpus節(jié)點
至少一個/memory節(jié)點
使用說明:R = 必需,O = 可選,OR = 可選但推薦,SD = 參見定義,所有其他的標準屬性均可接受,但可選
devicetree有一個單獨的根節(jié)點,所有其他設(shè)備節(jié)點都是它的后代。根節(jié)點的完整路徑為/。
設(shè)備樹文件可能具有一個別名節(jié)點(/aliases),該節(jié)點定義一個或多個別名屬性。別名節(jié)點應(yīng)位于設(shè)備樹的根節(jié)點,并且具有節(jié)點名稱/別名。/aliases節(jié)點的每個屬性都定義了一個別名。屬性名稱指定別名。屬性值指定設(shè)備樹中節(jié)點的完整路徑。例如,屬性serial0 = "/simple-bus@fe000000/serial@llc500"定義了別名serial0。別名的命名規(guī)則如下:
所有設(shè)備樹都需要內(nèi)存設(shè)備節(jié)點,并描述系統(tǒng)的物理內(nèi)存布局。如果系統(tǒng)具有多個范圍的內(nèi)存,則可以創(chuàng)建多個內(nèi)存節(jié)點,或者可以在單個內(nèi)存節(jié)點的reg屬性中指定范圍。
/memory節(jié)點的屬性要求如下:
在xxx.dts中
memory { reg = <0x40000000 0x10000000>; 起始地址0x40000000 長度0x10000000(32MB) };
/chosen 節(jié)點不代表系統(tǒng)中的實際設(shè)備,而是描述了在運行時由系統(tǒng)固件選擇或指定的參數(shù)。它應(yīng)該是根節(jié)點的子節(jié)點。
示例:
chosen { bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"; };
所有設(shè)備樹均需要/cpus/cpu節(jié)點。它并不代表系統(tǒng)中的真實設(shè)備,而是作為代表系統(tǒng)cpu的子cpu節(jié)點的容器。
在設(shè)備樹中,存在邏輯中斷樹,該邏輯中斷樹表示平臺硬件中斷的層次結(jié)構(gòu)和路由。在設(shè)備樹中,使用interrupt-parent屬性表示中斷源與中斷控制器的物理連線。代表產(chǎn)生中斷的設(shè)備節(jié)點包含一個中斷父屬性,該屬性具有一個虛擬值,指向給設(shè)備的中斷所路由到的設(shè)備(通常是中斷控制器)。
如果產(chǎn)生中斷的設(shè)備不具有中斷父屬性,則假定其中斷父節(jié)點為其設(shè)備父節(jié)點。每個中斷產(chǎn)生設(shè)備都包含一個中斷屬性,該屬性的值描述該設(shè)備的一個或多個中斷源。每個源都用稱為中斷描述符表示。中斷描述符的格式和含義是特定于中斷域的,即,取決于中斷域根節(jié)點上節(jié)點的屬性。中斷域的根使用#interrupt-cells屬性定義對中斷描述符進行編碼所需的值數(shù)量。
中斷域是解釋中斷描述符的上下文。中斷域的根可以是中斷控制器(interrupt controller)或中斷連接器(interrupt nexus):
中斷控制器是物理設(shè)備,需要一個驅(qū)動程序來處理通過它路由的中斷。它還可能級聯(lián)到另一個中斷域。中斷控制器由設(shè)備樹中該節(jié)點上的interrupt-controller指定。
中斷連接器定義了一個中斷域和另一個中斷域之間的轉(zhuǎn)換。翻譯基于特定領(lǐng)域和總線的信息。使用interrupt-map屬性在域之間進行轉(zhuǎn)換。例如,PCI控制器設(shè)備節(jié)點可以是一個中斷連接器,定義從PCI中斷命名空間(INTA、INTB等)到具有中斷請求(IRQ)編號的中斷控制器的轉(zhuǎn)換。
示例:
interrupts = <GIC_SPI INT_DMA IRQ_TYPE_LEVEL_HIGH>;
示例:
interrupt-parent = <&gpe>;
示例:
interrupts-extended = <&pic 0xA 8>, <&gic 0xda>;
Devicetree Blob (DTB)格式是Devicetree數(shù)據(jù)的平面二進制編碼。它用來在軟件程序之間交換設(shè)備數(shù)據(jù)。例如,在引導(dǎo)操作系統(tǒng)時,固件將向操作系統(tǒng)內(nèi)核傳遞一個DTB。
DTB格式將devicetree數(shù)據(jù)編碼為一個單一的、線性的、無指針的數(shù)據(jù)結(jié)構(gòu)。它由一個小標題組成,接下來是三個大小可變的部分:內(nèi)存保留塊、結(jié)構(gòu)塊和字符串塊這些應(yīng)該按照這個順序出現(xiàn)在扁平的devicetree中。
因此。當按地址加載到內(nèi)存中時,設(shè)備樹結(jié)構(gòu)作為一個整體。將類似于圖中的圖表。
設(shè)備樹的頭部是由以下C結(jié)構(gòu)體定義的。所有字段都是32位整數(shù),以big-endian格式存儲。
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)的所有部分:報頭、內(nèi)存預(yù)留塊、結(jié)構(gòu)塊和字符串塊,以及塊之間或最終塊之后的自由空間間隙。 uint32_t totalsize; /* total size of DT block */ 此字段應(yīng)包含結(jié)構(gòu)塊從標題開始的字節(jié)偏移 uint32_t off_dt_struct; /* offset to structure */ 此字段應(yīng)包含從標題開始的字符串塊的字節(jié)偏移量 uint32_t off_dt_strings; /* offset to strings */ 此字段應(yīng)包含從標題開始的內(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é)點的reg屬性中給定的物理ID相同 uint32_t boot_cpuid_phys; /* Which physical CPU id we're booting on */ /* version 3 fields below */ 此字段應(yīng)包含字符串塊部分的字節(jié)長度 uint32_t size_dt_strings; /* size of the strings block */ /* version 17 fields below */ 此字段應(yīng)包含結(jié)構(gòu)塊部分的字節(jié)長度 uint32_t size_dt_struct; /* size of the structure block */ };
內(nèi)存保留塊向客戶端程序提供物理內(nèi)存中被保留的區(qū)域的列表,這些內(nèi)存不用于一般的內(nèi)存分配,目的是保護重要的數(shù)據(jù)結(jié)構(gòu)不被客戶端程序覆蓋。這個區(qū)域包括了若干的reserve memory描述符。每個reserve memory描述符是由address和size組成。其中address和size都是用U64來描述:
struct fdt_reserve_entry { uint64_t address; uint64_t size; };
結(jié)構(gòu)塊描述了設(shè)備樹本身的結(jié)構(gòu)和內(nèi)容。它由若干的分片組成,每個分片開始位置都是保存了令牌(token),以此來描述該分片的屬性和內(nèi)容。
FDT_BEGIN_NODE (0x00000001):該token描述了一個node的開始位置,緊挨著該token的就是node name(包括unit address)
FDT_END_NODE (0x00000002):該token描述了一個node的結(jié)束位置
FDT_PROP (0x00000003):該token描述了一個property的開始位置,該token之后是兩個u32的數(shù)據(jù)。它們之后就是長度為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):標記結(jié)構(gòu)塊的結(jié)束 所以,一個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
定義了各個node中使用的屬性的字符串表。由于很多屬性會出現(xiàn)在多個node中,因此,所有的 屬性字符串組成了一個string block。這樣可以壓縮DTB的size。
設(shè)備樹描述了設(shè)備的詳細信息,這些信息包括數(shù)字類型的、字符串類型的、數(shù)組類型的,我們在編寫驅(qū)動時需要去獲取這些信息。Linux內(nèi)核提供一系列以of_開頭的函數(shù)來獲取設(shè)備樹信息,這些函數(shù)的原型都定義在include/linux/of.h中。設(shè)備以節(jié)點的形式掛在設(shè)備樹上,Linux內(nèi)核使用device_node結(jié)構(gòu)體來描述一個節(jié)點,其定義在include/linux/of.h中:
struct device_node { const char *name; device node name const char *type; 對應(yīng)device_type的屬性 phandle phandle; 對應(yīng)該節(jié)點的phandle屬性 const char *full_name; 從“/”開始的,表示該node的full path Struct property *properties; 該節(jié)點的屬性列表 如果需要刪除某些屬性,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 };
功能 : 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é)點,如果為NULL表示從根節(jié)點開始查找整個設(shè)備樹。 @name::要查找的節(jié)點名字。
返回值: 找到的節(jié)點,如果為NULL表示查找失敗。
功能 : Find a node matching a full OF path 函數(shù) :
struct device_node *of_find_node_by_path(const char *path)
參數(shù) : @path: 完整的匹配路徑 返回值 : 找到的節(jié)點,如果為NULL表示查找失敗。
功能 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é)點,如果為NULL表示從根節(jié)點開始查找整個設(shè)備樹 @type: 要查找的節(jié)點類型
返回值 找到的節(jié)點,如果為NULL表示查找失敗。
功能 通過device_type和compatible查找指定節(jié)點 函數(shù)
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)
參數(shù)
@from:開始查找的節(jié)點,如果為NULL表示從根節(jié)點開始查找整個設(shè)備樹 @type: 要查找的節(jié)點device_type屬性 @compatible:節(jié)點的compatible屬性列表
返回值 找到的節(jié)點,如果為NULL表示查找失敗。
功能 通過屬性名查找指定節(jié)點 函數(shù)
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)
參數(shù)
@from:開始查找的節(jié)點,如果為NULL表示從根節(jié)點開始查找整個設(shè)備樹 @type: 要查找的節(jié)點屬性名稱
返回值 找到的節(jié)點,如果為NULL表示查找失敗。
功能 函數(shù)用于獲取指定節(jié)點的父節(jié)點(如果有父節(jié)點的話 ) 函數(shù)
struct device_node *of_get_parent(const struct device_node *node)
參數(shù)
@node:要查找父節(jié)點的節(jié)點
返回值 找到的父節(jié)點
功能 獲取子節(jié)點,并跳過status = "disabled"的節(jié)點 函數(shù)
struct device_node *of_get_next_available_child(const struct device_node *node,struct device_node *prev)
參數(shù)
@node: 父節(jié)點 @prev:當前父節(jié)點的上一個子節(jié)點, 如果為空,則獲取第一個子節(jié)點
返回值 找到的子節(jié)點
Linux內(nèi)核使用struct property來保存節(jié)點的屬性,其定義在/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; };
功能 尋找指定的屬性 函數(shù)
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp)
參數(shù)
@np: 設(shè)備節(jié)點 @name:屬性名稱 @lenp:屬性的字節(jié)數(shù)
返回值 找到的屬性
當設(shè)置sz為1時,就是讀取一個數(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)
功能 找到并讀取屬性字符串 函數(shù)
int of_property_read_string(struct device_node *np, const char *propname,const char **out_string)
參數(shù)
@np: 設(shè)備節(jié)點 @propname:屬性名稱 @out_string:讀取的字符串
返回值
0:讀取成功 -EINVAL:屬性不存在 -ENODATA:屬性沒有這個值 -EILSEQ:字符串不是以空字符’\0’結(jié)尾
在uboot引導(dǎo)內(nèi)核的時候,會將設(shè)備樹在物理內(nèi)存中的物理起始內(nèi)存地址傳遞給Linux內(nèi)核,然后Linux內(nèi)核在unflattern_device_tree中解析設(shè)備鏡像,并利用掃描到的信息創(chuàng)建由device node構(gòu)成的鏈表,全局變量of_allnodes指向鏈表的根節(jié)點,設(shè)備樹的每一個節(jié)點都由一個struct device_node與之對應(yīng)。unflatten_device_tree的意思是解開設(shè)備樹,在這個函數(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)在為止,我們得到了一個名為of_allnodes的struct *device_node,它指向了設(shè)備樹展開后的device_node樹,后續(xù)的操作都是基于device_node樹。
內(nèi)核從啟動到創(chuàng)建設(shè)備的過程大致如下:在do_initcalls中會傳遞level給do_initcall_level來調(diào)用不同層次的初始化函數(shù),level的對應(yīng)關(guān)系見linux-3.10/include/linux/init.h 第196行。在這個初始化過程中,會調(diào)用一個customize_machine的函數(shù)。
此節(jié)分析Platform driver的注冊流程,以memctrl驅(qū)動的注冊為例分析。關(guān)于系統(tǒng)調(diào)用驅(qū)動初始化函數(shù)的流程分析,參考自動初始化機制章節(jié)。本章節(jié)分析從設(shè)備驅(qū)動文件的xxx_init函數(shù)開始分析。
platform_driver是在device_driver之上的一層封裝,其結(jié)構(gòu)如下:
struct platform_driver { int (*probe)(struct platform_device *); 探測函數(shù) int (*remove)(struct platform_device *); 驅(qū)動卸載時執(zhí)行 void (*shutdown)(struct platform_device *); 關(guān)機時執(zhí)行函數(shù) int (*suspend)(struct platform_device *, pm_message_t state); 掛起函數(shù) int (*resume)(struct platform_device *); 恢復(fù)函數(shù) struct device_driver driver; 管理的driver對象 const struct platform_device_id *id_table; 匹配時使用 };
struct device_driver是系統(tǒng)提供的基本驅(qū)動結(jié)構(gòu):
struct device_driver { const char *name; 驅(qū)動名稱 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); 探測設(shè)備 int (*remove) (struct device *dev); 與設(shè)備脫離時調(diào)用 void (*shutdown) (struct device *dev); 在關(guān)機時關(guān)閉設(shè)備 int (*suspend) (struct device *dev, pm_message_t state); 使設(shè)備進入睡眠模式調(diào)用 int (*resume) (struct device *dev); 喚醒設(shè)備時調(diào)用 const struct attribute_group **groups; 自動創(chuàng)建的默認屬性組 const struct dev_pm_ops *pm; 設(shè)備的功耗管理 struct driver_private *p; 驅(qū)動的私有數(shù)據(jù) };
Platform_driver的注冊接口是platform_driver_register,其定義如下:
int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; 設(shè)置總線類型 if (drv->probe) 確認定義了probe函數(shù) drv->driver.probe = platform_drv_probe; 里面實際調(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接口是為注冊總線驅(qū)動做一些準備工作,定義了總線類型,設(shè)置了driver的部分接口,最后driver_register會向總線注冊驅(qū)動
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ū)動是否已經(jīng)注冊 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ū)動屬性 if (ret) { bus_remove_driver(drv); return ret; } kobject_uevent(&drv->p->kobj, KOBJ_ADD); 涉及到uevent,暫時不分析 return ret; }
由以上分析可知,驅(qū)動的注冊,重點在bus_add_driver()函數(shù),它會向總線添加驅(qū)動:
Drivers/base/bus.c int bus_add_driver(struct device_driver *drv) { struct bus_type *bus; struct driver_private *priv; 包含與驅(qū)動相關(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è)置了自動探測 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; }
driver_attach會嘗試綁定設(shè)備和驅(qū)動。編譯總線上的所有設(shè)備,然驅(qū)動挨個嘗試匹配,如果driver_probe_device()返回0且@dev->driver被設(shè)置,就代表找到了一對兼容的設(shè)備驅(qū)動。
int driver_attach(struct device_driver *drv) { return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); } EXPORT_SYMBOL_GPL(driver_attach);
對于每一個總線的設(shè)備,driver_attach都會調(diào)用__driver_attach來嘗試與驅(qū)動匹配。
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ū)動,這里調(diào)用的是platform_match return 0; if (dev->parent) /* Needed for USB */ device_lock(dev->parent); device_lock(dev); 設(shè)置互斥鎖,防止其他進程訪問設(shè)備資源 if (!dev->driver) 如果設(shè)備沒有驅(qū)動,則為設(shè)備探測驅(qū)動,這個函數(shù)與注冊設(shè)備調(diào)用的是同一個函數(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ū)動文件中的probe函數(shù),對于memctrl驅(qū)動而言,就是xxxx_memctrl_probe函數(shù)。至此,platfprm driver就注冊好了。
由以上的代碼分析得知,注冊platform device時,會調(diào)用__device_attach -> driver_match_device,注冊platform driver時,會調(diào)用__driver_attach -> driver_match_device,也就是說設(shè)備和驅(qū)動都會調(diào)用到這個函數(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ū)動綁定的總線提供的匹配函數(shù),這里注冊的是platform總線設(shè)備,而platform總線的定義參考3.2.6 platform_bus_type。Platform對應(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); }
根據(jù)驅(qū)動的of_match_table判斷是否有驅(qū)動與之匹配。對memctrl驅(qū)動而言,其of_match_table如下:
static struct of_device_id xxxx_memctrl_of_match[] = { { .compatible = "xxxx,memctrl", }, {}, };
of_driver_match_device的執(zhí)行流程如下:
所以重點應(yīng)該在__of_match_node函數(shù):
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]) 查找屬性,檢測節(jié)點的compatible是否與驅(qū)動的一致 match &= __of_device_is_compatible(node, matches->compatible); if (match) return matches; matches++; } return NULL; }
Linux中每一個模塊都有一個module_init函數(shù),并且有且只有一個,其定義如下:
/** * 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)在按照功能分組到單獨的子部分。子部分內(nèi)部的順序由鏈接順序決定。為了向后兼容,initcall()將調(diào)用放到device init小節(jié)中。需要定義initcall()的’id’參數(shù),以便多個initcall可以指向同一個處理程序,而不會導(dǎo)致重復(fù)符號構(gòu)建錯誤。若不理解上述代碼的用法,可以參考__attribute__的section用法和C語言宏定義中#和##的用法。所以將__define_initcall展開將會是下面的內(nèi)容:
假設(shè)__define_initcall(led_init, 6) Static initcall_t __initcall_led_init6 __used \ __attribute__((__section__(".initcall6.init"))) = led_init
即是定義了一個類型為initcall_t的函數(shù)指針變量__initcall_led_init6,并賦值為led_init,該變量在鏈接時會鏈接到section(.initcall6.init)。
在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)
內(nèi)核啟動流程為:
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)核會依次調(diào)用level段存儲的初始化函數(shù)。比如對于模塊來說level等于6。
如果設(shè)置為編譯成動態(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)的作用就是定義一個變量名 init_module,其地址和initfn是一樣的。
編譯成module的模塊都會自動產(chǎn)生一個*.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")。
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)用對應(yīng)內(nèi)核層的sys_init_module函數(shù)
路徑: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數(shù)據(jù)結(jié)構(gòu) mod = (void *)sechdrs[modindex].sh_addr; if (mod->init != NULL) ret = do_one_initcall(mod->init); //調(diào)用initfn.
__define_initcall使用了gcc的 __attribute__眾多屬性中的section子項,其使用方式為:
__attribute__((__section__("section_name")))
其作用是將作用的函數(shù)或數(shù)據(jù)放入指定的名為”section_name”的段。
我們使用#把宏參數(shù)變?yōu)橐粋€字符串。
#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
這是因為”The value of”#VALUE”is ” FORMAT”\n”實際上是包含了”The value of “,#VALUE,”is “,FORMAT,”\n” 五部分字符串,其中VALUE和FORMAT被宏參數(shù)的實際值替換了。
用##把兩個宏參數(shù)貼合在一起
#define ADD_TO_SUM(sum_number,val) sum##sum_bumber+=(val)
調(diào)用:ADD_TO_SUM(2,100); --> 打?。簊um2+=(100)
需要注意的是凡宏定義里有用'#'或'##'的地方宏參數(shù)是不會再展開。
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表示該行行號;第一層:ANONYMOUS(static int); --> __ANONYMOUS0(static int, LINE); 第二層: --> ___ANONYMOUS1(static int, _anonymous, 70); 第三層: --> static int _anonymous70; 即每次只能解開當前層的宏,所以__LINE__在第二層才能被解開;
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)};
相當于:
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.得到一個數(shù)值類型所對應(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];
這里相當于:
char buf[11];
上述就是小編為大家分享的如何讓Linux驅(qū)動11-Linux設(shè)備驅(qū)動統(tǒng)一模型了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。