溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時間:2021-09-27 17:03:10 來源:億速云 閱讀:169 作者:柒染 欄目:系統(tǒng)運維

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

1. 設(shè)備樹概念

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

設(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é)點。

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

1.2.DTS、DTB、DTC

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文件。

1.3.Device Tree語法

以下語法分析均以xxx.dts為例。

1.3.1. dtsi頭文件

設(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é)點。

1.3.2. 設(shè)備節(jié)點

設(shè)備樹中的每一個節(jié)點都按照以下格式命名:

node-name@unit-address

node-name表示節(jié)點名稱,它的長度范圍應(yīng)該是1~31個字符,可以由以下的字符組成:

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

表 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é)點名稱或單位地址。它由正斜杠(/)標識。

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

圖 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來訪問。

1.3.2.1. 通用名稱建議

節(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

1.3.2.2. 路徑名稱

通過指定從根節(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。

1.3.3. 屬性

設(shè)備樹中的每個節(jié)點都有描述節(jié)點特性的屬性。屬性由名稱和值組成。

1.3.3.1. 屬性名稱

屬性名稱的長度范圍應(yīng)該是1~31個字符,可以由以下的字符組成:

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

非標準屬性名稱應(yīng)指定唯一的字符串前綴,例如股票代號,用于標識定義該屬性的公司或組織的名稱。示例:

xxx,pin-function = <6>; fsl,channel-fifo-len   linux,network-index   ibm,ppc-interrupt-server#s

1.3.3.2. 屬性值

屬性值是包含與屬性關(guān)聯(lián)的信息的零或多個字節(jié)的數(shù)組。

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

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

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

1.3.3.3. 標準屬性

1.Compatible(兼容)

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

示例:

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(型號)

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

示例:

model =“fsl,MPC8349EMITX”;

3.Phandle(pointer handle)

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

示例:

pic@10000000 { phandle = <1>; interrupt-controller; };

定義了1的phandle值。另一個設(shè)備節(jié)點可以引用phandle值為1的pic節(jié)點:

another-device-node { interrupt-parent = <1>; };

4.Status

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

5.#address-cells and #size-cells

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

#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

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

示例:假設(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

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

8.Ranges

如何讓Linux驅(qū)動11-Linux設(shè)備驅(qū)動統(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é)點指定了<0x0 0xe0000000  0x00100000>;此屬性值指定對于1024KB范圍的地址空間,在物理0x0處尋址的子節(jié)點映射到物理0xe0000000的父地址。通過這種映射,串行設(shè)備節(jié)點可以通過0xe0004600地址的加載或存儲、0x4600(在注冊表中指定)的偏移量以及范圍中指定的0xe0000000映射尋址。

9.dma-ranges

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

10.Name(已棄用)

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

11.device_type

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

1.3.4. 基本設(shè)備節(jié)點類型

所有設(shè)備樹文件均要包含一個根文件,并且所有設(shè)備樹文件均應(yīng)在根節(jié)點下存在以下節(jié)點:

  • 1個/cpus節(jié)點

  • 至少一個/memory節(jié)點

使用說明:R = 必需,O = 可選,OR = 可選但推薦,SD = 參見定義,所有其他的標準屬性均可接受,但可選

1.3.4.1. Root node

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

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

1.3.4.2. /aliases節(jié)點

設(shè)備樹文件可能具有一個別名節(jié)點(/aliases),該節(jié)點定義一個或多個別名屬性。別名節(jié)點應(yīng)位于設(shè)備樹的根節(jié)點,并且具有節(jié)點名稱/別名。/aliases節(jié)點的每個屬性都定義了一個別名。屬性名稱指定別名。屬性值指定設(shè)備樹中節(jié)點的完整路徑。例如,屬性serial0  = "/simple-bus@fe000000/serial@llc500"定義了別名serial0。別名的命名規(guī)則如下:

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

1.3.4.3. /memory節(jié)點

所有設(shè)備樹都需要內(nèi)存設(shè)備節(jié)點,并描述系統(tǒng)的物理內(nèi)存布局。如果系統(tǒng)具有多個范圍的內(nèi)存,則可以創(chuàng)建多個內(nèi)存節(jié)點,或者可以在單個內(nèi)存節(jié)點的reg屬性中指定范圍。

/memory節(jié)點的屬性要求如下:

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

在xxx.dts中

memory {        reg =  <0x40000000 0x10000000>;   起始地址0x40000000 長度0x10000000(32MB)  };

1.3.4.4. /chosen 節(jié)點

/chosen 節(jié)點不代表系統(tǒng)中的實際設(shè)備,而是描述了在運行時由系統(tǒng)固件選擇或指定的參數(shù)。它應(yīng)該是根節(jié)點的子節(jié)點。

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

示例:

chosen { bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"; };

1.3.4.5. /cpus節(jié)點屬性

所有設(shè)備樹均需要/cpus/cpu節(jié)點。它并不代表系統(tǒng)中的真實設(shè)備,而是作為代表系統(tǒng)cpu的子cpu節(jié)點的容器。

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

1.3.5. 中斷映射

在設(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)換。

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

1.3.5.1. Interrupts

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

示例:

interrupts = <GIC_SPI INT_DMA IRQ_TYPE_LEVEL_HIGH>;

1.3.5.2. interrupt-parent

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

示例:

interrupt-parent = <&gpe>;

1.3.5.3. interrupts-extended

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

示例:

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

1.3.5.4. #interrupt-cells

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

1.3.5.5. interrupt-controller

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

1.4.Device Tree binary格式

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)作為一個整體。將類似于圖中的圖表。

1.4.1. dt_header

設(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 */ };

1.4.2. memory reservation 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; };

1.4.3. Structure block

結(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&rsquo;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中使用的屬性的字符串表。由于很多屬性會出現(xiàn)在多個node中,因此,所有的 屬性字符串組成了一個string  block。這樣可以壓縮DTB的size。

1.5.Linux解析設(shè)備樹

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

1.5.1. 查找節(jié)點的 OF函數(shù)

1.5.1.1. of_find_node_by_name

功能 : Find a node by its "name" property 函數(shù)

struct device_node *of_find_node_by_name(struct device_node *from,  const char *name)

參數(shù) :

@from:開始查找的節(jié)點,如果為NULL表示從根節(jié)點開始查找整個設(shè)備樹。 @name::要查找的節(jié)點名字。

返回值: 找到的節(jié)點,如果為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é)點,如果為NULL表示查找失敗。

1.5.1.3. of_find_node_by_type

功能 Find a node by its "device_type" property 函數(shù)

struct device_node *of_find_node_by_type(struct device_node *from,  const char *type)

參數(shù)

@from:開始查找的節(jié)點,如果為NULL表示從根節(jié)點開始查找整個設(shè)備樹 @type: 要查找的節(jié)點類型

返回值 找到的節(jié)點,如果為NULL表示查找失敗。

1.5.1.4. of_find_compatible_node

功能 通過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表示查找失敗。

1.5.1.5. of_find_node_with_property

功能 通過屬性名查找指定節(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表示查找失敗。

1.5.2. 查找父 /子節(jié)點的 OF函數(shù)

1.5.2.1. of_get_parent

功能 函數(shù)用于獲取指定節(jié)點的父節(jié)點(如果有父節(jié)點的話 ) 函數(shù)

struct device_node *of_get_parent(const struct device_node *node)

參數(shù)

@node:要查找父節(jié)點的節(jié)點

返回值 找到的父節(jié)點

1.5.2.2. of_get_next_available_child

功能 獲取子節(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é)點

1.5.3. 提取屬性值的 OF函數(shù)

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

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é)點 @name:屬性名稱 @lenp:屬性的字節(jié)數(shù)

返回值 找到的屬性

1.5.3.2. 讀取屬性中u8、u16、u32和u64類型的數(shù)組數(shù)據(jù)

當設(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)

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é)點 @propname:屬性名稱 @out_string:讀取的字符串

返回值

0:讀取成功 -EINVAL:屬性不存在 -ENODATA:屬性沒有這個值 -EILSEQ:字符串不是以空字符&rsquo;\0&rsquo;結(jié)尾

2. 設(shè)備樹解析流程

2.1.內(nèi)核啟動并獲取設(shè)備樹

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

2.2.創(chuàng)建platform_device

內(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ù)。

2.3.Platform driver注冊流程

此節(jié)分析Platform  driver的注冊流程,以memctrl驅(qū)動的注冊為例分析。關(guān)于系統(tǒng)調(diào)用驅(qū)動初始化函數(shù)的流程分析,參考自動初始化機制章節(jié)。本章節(jié)分析從設(shè)備驅(qū)動文件的xxx_init函數(shù)開始分析。

2.3.1. struct platform_driver

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;   匹配時使用 };

2.3.2. struct device_driver

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

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;  設(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ū)動

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ū)動是否已經(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; }

2.3.5. bus_add_driver

由以上分析可知,驅(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; }

2.3.6. driver_attach

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

2.3.7. __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就注冊好了。

2.4.Platform Bus的匹配原則

由以上的代碼分析得知,注冊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); }

2.4.1. of_driver_match_device

根據(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ù):

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])  查找屬性,檢測節(jié)點的compatible是否與驅(qū)動的一致        match &= __of_device_is_compatible(node, matches->compatible);   if (match)        return matches;       matches++;  }  return NULL; }

3. 使用設(shè)備資源

4. 自動初始化機制

4.1.編譯到內(nèi)核

4.1.1. module_init宏展開

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()的&rsquo;id&rsquo;參數(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)。

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. 初始化

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

如何讓Linux驅(qū)動11-Linux設(shè)備驅(qū)動統(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)核會依次調(diào)用level段存儲的初始化函數(shù)。比如對于模塊來說level等于6。

4.2.動態(tài)加載的模塊(.ko)

4.2.1. Module_init展開

如果設(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是一樣的。

4.2.2. *mod.c文件

編譯成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")。

4.2.3. 動態(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)用對應(yīng)內(nèi)核層的sys_init_module函數(shù)

路徑:kernel/module.c

SYSCALL_DEFINE3(init_module,&hellip;)  //加載模塊的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.

4.3.__attribute__的section用法

__define_initcall使用了gcc的 __attribute__眾多屬性中的section子項,其使用方式為:

__attribute__((__section__("section_name")))

其作用是將作用的函數(shù)或數(shù)據(jù)放入指定的名為”section_name”的段。

4.4. C語言宏定義中#和##的用法

4.4.1. 一般用法

我們使用#把宏參數(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ù)是不會再展開。

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表示該行行號;第一層: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è)資訊頻道。

向AI問一下細節(jié)

免責(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)容。

AI