1. 設備樹概念
1.1.設備樹感性認識設
備樹(Device Tree),將這個詞分開就是“設備”和“樹”,描述設備樹的文件叫做DTS(Device Tree Source),這個DTS 文件采用樹形結構描述板級設備,比如CPU 數(shù)量、 內(nèi)存基地址、IIC 接口上接了哪些設備、SPI 接口上接了哪些設備等等。設備樹是樹形數(shù)據(jù)結構,具有描述系統(tǒng)中設備的節(jié)點。每個節(jié)點都有描述所代表設備特征的鍵值對。每個節(jié)點只有一個父節(jié)點,而根節(jié)點則沒有父節(jié)點。
1.2.DTS、DTB、DTC
DTS:設備樹源碼文件;DTB:將DTS編譯后得到的二進制文件;DTC:DTS的編譯工具,其源碼在內(nèi)核的scriptsdtc目錄下?;谕瑯觓rm架構的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ā)板,只要設置CONFIG_ARCH_xxx=y,所有用到這顆SOC的DTS都會編譯成DTB。如果后續(xù)還用到了這顆SOC設計的開發(fā)板,只要新建一個DTS文件,并將對應名稱的DTB文件名加到dtb-$(CONFIG_ARCH_xxx)中,在編譯設備樹時就會將DTS編譯為二進制的DTB文件。
1.3.Device Tree語法
以下語法分析均以xxx.dts為例。
1.3.1. dtsi頭文件
設備樹的頭文件擴展名為 .dtsi。以xxx.dts為例,其包含以下頭文件。
#include?"skeleton.dtsi"
#include?xxx.h"
#include?"xxx-clocks.dtsi"
#include?"xxx-pinctrl.dtsi"
#include?"xxx-camera.dtsi"
需要注意的是.dts文件不但可以引用.dtsi文件,還可以引用.h文件和其他的.dts文件。Q1:每一個.dtsi和.dts都有自己的根節(jié)點,但是一個設備樹文件只允許有一個根節(jié)點,DTC如何處理?將根節(jié)點合并,保留最后一級的根節(jié)點。包含的頭文件內(nèi)容會被展開,展開的位置在/memory和/cpus之間。(存疑,只用xxx.dts編譯過)Q2:如果包含過程中有重復的compatible,DTC怎么處理?編譯時不會報錯,會生成兩個compatible屬性一樣的節(jié)點。
1.3.2. 設備節(jié)點
設備樹中的每一個節(jié)點都按照以下格式命名:
node-name@unit-address
node-name表示節(jié)點名稱,它的長度范圍應該是1~31個字符,可以由以下的字符組成:
表 2-1節(jié)點名稱的有效字符
節(jié)點名稱應以較低或大寫字符開頭,并應描述設備的一般類別。節(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ū)分。在設備樹中經(jīng)常會看到以下設備名稱:
watchdog:?watchdog@04009800
冒號前的是節(jié)點標簽(label),冒號后是節(jié)點名稱。引入label的目的是方便訪問節(jié)點,可以直接通過&label來訪問這個節(jié)點。比如上述節(jié)點就可以使用&watchdog來訪問。
1.3.2.1. 通用名稱建議
節(jié)點的名稱應該有些通用,反映設備的功能,而不是其精確的編程模型。如適用,名稱應為以下選擇之一:
??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é)點。指定設備路徑的約定是:
/node-name-1/node-name-2/.../node-name-N
例如,在圖2-1中,到cpu#1的設備路徑為:
/cpus/cpu@1
/為根節(jié)點,在保證完整路徑明確的前提下,可以省略uint-address。
1.3.3. 屬性
設備樹中的每個節(jié)點都有描述節(jié)點特性的屬性。屬性由名稱和值組成。
1.3.3.1. 屬性名稱
屬性名稱的長度范圍應該是1~31個字符,可以由以下的字符組成:
非標準屬性名稱應指定唯一的字符串前綴,例如股票代號,用于標識定義該屬性的公司或組織的名稱。示例:
xxx,pin-function?=?<6>;
fsl,channel-fifo-len
linux,network-index
ibm,ppc-interrupt-server#s
1.3.3.2. 屬性值
屬性值是包含與屬性關聯(lián)的信息的零或多個字節(jié)的數(shù)組。
big-endian和little-endian(大小端):big-endian:是指低地址端存放高位字節(jié);little-endian:是指低地址端存放低位字節(jié);
1.3.3.3. 標準屬性
Compatible(兼容)
示例:
compatible?=“fsl,mpc8641”,“ns16550”;
在此示例中,操作系統(tǒng)將首先嘗試查找支持fsl,mpc8641-uartmpc8641的設備驅(qū)動程序。如果找不到驅(qū)動程序,然后,它將嘗試定位受支持的更通用的ns16550設備類型驅(qū)動程序 。
一般驅(qū)動程序文件都會有個OF匹配表,此匹配表保存著一些compatible值,如果設備節(jié)點的 compatible屬性值和OF匹配表中的任何一個值相等,那么就表示設備可以使用這驅(qū)動。比如在文件drvier/misc/memctrl.c中:
static?struct?of_device_id_xxx_memctrl_of_match[]?=?{
{?.compatible?=?"xxxx,memctrl",?},
{},
};
對應的,在arch/arm/boot/dts/xxx.dts中有:
memctrl:?memctrl?{
compatible?=?"xxxx,memctrl";
reg?=?<0x0121B000?0x1044>;
clocks?=?<&sdram_bandw_clk>,?<&mem_axi_clk>;
clock-names?=?"sdram_bandwidth_clk",?"mem_axi_clk";
interrupts?=?<GIC_SPI?INT_SDRAM?IRQ_TYPE_LEVEL_HIGH>;
interrupt-controller;
#interrupt-cells?=?<1>;
};
Model(型號)
示例:
model =“fsl,MPC8349EMITX”;
Phandle(pointer handle)
示例:
pic@10000000?{
phandle?=?<1>;
interrupt-controller;
};
定義了1的phandle值。另一個設備節(jié)點可以引用phandle值為1的pic節(jié)點:
another-device-node?{
interrupt-parent?=?<1>;
};
Status
#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
Reg
示例:假設系統(tǒng)芯片中的設備包含兩個寄存器塊,SOC中偏移0x3000的32字節(jié)塊和偏移0xFE00的256字節(jié)塊。reg屬性的編碼如下(假設#address-cells和#size-cells值為1):
reg=<0x3000?0x20?0xFE00?0x100>;
virtual-reg
range
示例:
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的父地址。通過這種映射,串行設備節(jié)點可以通過0xe0004600地址的加載或存儲、0x4600(在注冊表中指定)的偏移量以及范圍中指定的0xe0000000映射尋址。
dma-range
Name(已棄用)
device_type
1.3.4. 基本設備節(jié)點
類型所有設備樹文件均要包含一個根文件,并且所有設備樹文件均應在根節(jié)點下存在以下節(jié)點:
- 1個/cpus節(jié)點
- 至少一個/memory節(jié)點
使用說明:R = 必需,O = 可選,OR = 可選但推薦,SD = 參見定義,所有其他的標準屬性均可接受,但可選
1.3.4.1. Root node
devicetree有一個單獨的根節(jié)點,所有其他設備節(jié)點都是它的后代。根節(jié)點的完整路徑為/。
1.3.4.2. /aliases節(jié)點
設備樹文件可能具有一個別名節(jié)點(/aliases),該節(jié)點定義一個或多個別名屬性。別名節(jié)點應位于設備樹的根節(jié)點,并且具有節(jié)點名稱/別名。/aliases節(jié)點的每個屬性都定義了一個別名。屬性名稱指定別名。屬性值指定設備樹中節(jié)點的完整路徑。例如,屬性serial0 = "/simple-bus@fe000000/serial@llc500"定義了別名serial0。別名的命名規(guī)則如下:
1.3.4.3. /memory節(jié)點
所有設備樹都需要內(nèi)存設備節(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)
};
1.3.4.4. /chosen 節(jié)點
示例:
chosen?{
bootargs?=?"root=/dev/nfs?rw?nfsroot=192.168.1.1?console=ttyS0,115200";
};
1.3.4.5. /cpus節(jié)點屬性
所有設備樹均需要/cpus/cpu節(jié)點。它并不代表系統(tǒng)中的真實設備,而是作為代表系統(tǒng)cpu的子cpu節(jié)點的容器。
1.3.5. 中斷映射
在設備樹中,存在邏輯中斷樹,該邏輯中斷樹表示平臺硬件中斷的層次結構和路由。在設備樹中,使用interrupt-parent屬性表示中斷源與中斷控制器的物理連線。代表產(chǎn)生中斷的設備節(jié)點包含一個中斷父屬性,該屬性具有一個虛擬值,指向給設備的中斷所路由到的設備(通常是中斷控制器)。
如果產(chǎn)生中斷的設備不具有中斷父屬性,則假定其中斷父節(jié)點為其設備父節(jié)點。每個中斷產(chǎn)生設備都包含一個中斷屬性,該屬性的值描述該設備的一個或多個中斷源。每個源都用稱為中斷描述符表示。中斷描述符的格式和含義是特定于中斷域的,即,取決于中斷域根節(jié)點上節(jié)點的屬性。中斷域的根使用#interrupt-cells屬性定義對中斷描述符進行編碼所需的值數(shù)量。
中斷域是解釋中斷描述符的上下文。中斷域的根可以是中斷控制器(interrupt controller)或中斷連接器(interrupt nexus):
- 中斷控制器是物理設備,需要一個驅(qū)動程序來處理通過它路由的中斷。它還可能級聯(lián)到另一個中斷域。中斷控制器由設備樹中該節(jié)點上的interrupt-controller指定。
- 中斷連接器定義了一個中斷域和另一個中斷域之間的轉(zhuǎn)換。翻譯基于特定領域和總線的信息。使用interrupt-map屬性在域之間進行轉(zhuǎn)換。例如,PCI控制器設備節(jié)點可以是一個中斷連接器,定義從PCI中斷命名空間(INTA、INTB等)到具有中斷請求(IRQ)編號的中斷控制器的轉(zhuǎn)換。
1.3.5.1. Interrupts
示例:
interrupts?=?<GIC_SPI?INT_DMA?IRQ_TYPE_LEVEL_HIGH>;
1.3.5.2. interrupt-paren
示例:
interrupt-parent?=?<&gpe>;
1.3.5.3. interrupts-extended
示例:
interrupts-extended?=?<&pic?0xA?8>,?<&gic?0xda>;
1.3.5.4. #interrupt-cells
1.3.5.5. interrupt-controller
1.4.Device Tree binary格式
Devicetree Blob (DTB)格式是Devicetree數(shù)據(jù)的平面二進制編碼。它用來在軟件程序之間交換設備數(shù)據(jù)。例如,在引導操作系統(tǒng)時,固件將向操作系統(tǒng)內(nèi)核傳遞一個DTB。
DTB格式將devicetree數(shù)據(jù)編碼為一個單一的、線性的、無指針的數(shù)據(jù)結構。它由一個小標題組成,接下來是三個大小可變的部分:內(nèi)存保留塊、結構塊和字符串塊這些應該按照這個順序出現(xiàn)在扁平的devicetree中。
因此。當按地址加載到內(nèi)存中時,設備樹結構作為一個整體。將類似于圖中的圖表。
1.4.1. dt_header
設備樹的頭部是由以下C結構體定義的。所有字段都是32位整數(shù),以big-endian格式存儲。
struct?fdt_header?{
此字段應包含值0xd00dfeed(big-endian)
uint32_t?magic;????/*?magic?word?FDT_MAGIC?*/
此字段應包含設備數(shù)據(jù)結構的總大?。ㄗ止?jié))。該大小應包含結構的所有部分:報頭、內(nèi)存預留塊、結構塊和字符串塊,以及塊之間或最終塊之后的自由空間間隙。
uint32_t?totalsize;???/*?total?size?of?DT?block?*/
此字段應包含結構塊從標題開始的字節(jié)偏移
uint32_t?off_dt_struct;???/*?offset?to?structure?*/
此字段應包含從標題開始的字符串塊的字節(jié)偏移量
uint32_t?off_dt_strings;??/*?offset?to?strings?*/
此字段應包含從標題開始的內(nèi)存保留塊的字節(jié)偏移量
uint32_t?off_mem_rsvmap;??/*?offset?to?memory?reserve?map?*/
此字段應包含設備數(shù)據(jù)結構的版本
uint32_t?version;???/*?format?version?*/
此字段應包含設備所用版本向后兼容的最低版本數(shù)據(jù)結構
uint32_t?last_comp_version;??/*?last?compatible?version?*/
/*?version?2?fields?below?*/
此字段應包含系統(tǒng)引導CPU的物理ID。它應與設備樹中CPU節(jié)點的reg屬性中給定的物理ID相同
uint32_t?boot_cpuid_phys;??/*?Which?physical?CPU?id?we're?booting?on?*/
/*?version?3?fields?below?*/
此字段應包含字符串塊部分的字節(jié)長度
uint32_t?size_dt_strings;??/*?size?of?the?strings?block?*/
/*?version?17?fields?below?*/
此字段應包含結構塊部分的字節(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ù)結構不被客戶端程序覆蓋。這個區(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
結構塊描述了設備樹本身的結構和內(nèi)容。它由若干的分片組成,每個分片開始位置都是保存了令牌(token),以此來描述該分片的屬性和內(nèi)容。
- FDT_BEGIN_NODE (0x00000001):該token描述了一個node的開始位置,緊挨著該token的就是node name(包括unit address)
- FDT_END_NODE (0x00000002):該token描述了一個node的結束位置
- 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):被解析設備樹的程序忽略,可用于覆蓋其他屬性,以刪除它
- FDT_END (0x00000009):標記結構塊的結束所以,一個DTB的結構塊可能如下:
(optionally)?any?number?of?FDT_NOP?tokens
FDT_BEGIN_NODE?token:
--node’s?name
--paddings
For?each?property?of?the?node:
--FDT_NOP(optionally)
--FDT_PROP?token
--property
all?child?nodes?in?this?format
(optionally)?any?number?of?FDT_NOP?tokens
FDT_END_NODE?token
1.4.4. Strings Block
定義了各個node中使用的屬性的字符串表。由于很多屬性會出現(xiàn)在多個node中,因此,所有的
屬性字符串組成了一個string block。這樣可以壓縮DTB的size。
1.5.Linux解析設備樹
設備樹描述了設備的詳細信息,這些信息包括數(shù)字類型的、字符串類型的、數(shù)組類型的,我們在編寫驅(qū)動時需要去獲取這些信息。Linux內(nèi)核提供一系列以of_開頭的函數(shù)來獲取設備樹信息,這些函數(shù)的原型都定義在include/linux/of.h中。設備以節(jié)點的形式掛在設備樹上,Linux內(nèi)核使用device_node結構體來描述一個節(jié)點,其定義在include/linux/of.h中:
struct?device_node?{
const?char?*name;?????device?node?name
const?char?*type;?????對應device_type的屬性
phandle?phandle;??????對應該節(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é)點開始查找整個設備樹。
@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é)點開始查找整個設備樹
@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é)點開始查找整個設備樹
@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é)點開始查找整個設備樹
@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:?設備節(jié)點
@name:屬性名稱
@lenp:屬性的字節(jié)數(shù)
返回值找到的屬性
1.5.3.2. 讀取屬性中u8、u16、u32和u64類型的數(shù)組數(shù)據(jù)
當設置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:?設備節(jié)點
@propname:屬性名稱
@out_string:讀取的字符串
返回值
0:讀取成功
-EINVAL:屬性不存在
-ENODATA:屬性沒有這個值
-EILSEQ:字符串不是以空字符’’結尾
2. 設備樹解析流程
2.1.內(nèi)核啟動并獲取設備樹
在uboot引導內(nèi)核的時候,會將設備樹在物理內(nèi)存中的物理起始內(nèi)存地址傳遞給Linux內(nèi)核,然后Linux內(nèi)核在unflattern_device_tree中解析設備鏡像,并利用掃描到的信息創(chuàng)建由device node構成的鏈表,全局變量of_allnodes指向鏈表的根節(jié)點,設備樹的每一個節(jié)點都由一個struct device_node與之對應。
unflatten_device_tree的意思是解開設備樹,在這個函數(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,它指向了設備樹展開后的device_node樹,后續(xù)的操作都是基于device_node樹。
2.2.創(chuàng)建platform_device
內(nèi)核從啟動到創(chuàng)建設備的過程大致如下:在do_initcalls中會傳遞level給do_initcall_level來調(diào)用不同層次的初始化函數(shù),level的對應關系見linux-3.10/include/linux/init.h 第196行。在這個初始化過程中,會調(diào)用一個customize_machine的函數(shù)。
2.3.Platform driver
注冊流程此節(jié)分析Platform driver的注冊流程,以memctrl驅(qū)動的注冊為例分析。關于系統(tǒng)調(diào)用驅(qū)動初始化函數(shù)的流程分析,參考自動初始化機制章節(jié)。本章節(jié)分析從設備驅(qū)動文件的xxx_init函數(shù)開始分析。
2.3.1. struct platform_driver
platform_driver是在device_driver之上的一層封裝,其結構如下:
struct?platform_driver?{
int?(*probe)(struct?platform_device?*);???探測函數(shù)
int?(*remove)(struct?platform_device?*);??驅(qū)動卸載時執(zhí)行
void?(*shutdown)(struct?platform_device?*);??關機時執(zhí)行函數(shù)
int?(*suspend)(struct?platform_device?*,?pm_message_t?state);??掛起函數(shù)
int?(*resume)(struct?platform_device?*);?????恢復函數(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ū)動結構: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;?設備樹匹配表
const?struct?acpi_device_id??*acpi_match_table;?ACPI匹配表
int?(*probe)?(struct?device?*dev);??探測設備
int?(*remove)?(struct?device?*dev);?與設備脫離時調(diào)用
void?(*shutdown)?(struct?device?*dev);?在關機時關閉設備
int?(*suspend)?(struct?device?*dev,?pm_message_t?state);?使設備進入睡眠模式調(diào)用
int?(*resume)?(struct?device?*dev);??喚醒設備時調(diào)用
const?struct?attribute_group?**groups;?自動創(chuàng)建的默認屬性組
const?struct?dev_pm_ops?*pm;??設備的功耗管理
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;??設置總線類型
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ū)動做一些準備工作,定義了總線類型,設置了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?methodsn",?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ū)動相關的kobject和klist結構
int?error?=?0;
bus?=?bus_get(drv->bus);??獲取設備所屬的總線類型
if?(!bus)
return?-EINVAL;
pr_debug("bus:?'%s':?add?driver?%sn",?bus->name,?drv->name);
priv?=?kzalloc(sizeof(*priv),?GFP_KERNEL);
if?(!priv)?{
error?=?-ENOMEM;
goto?out_put_bus;
}
klist_init(&priv->klist_devices,?NULL,?NULL);
priv->driver?=?drv;
drv->p?=?priv;
priv->kobj.kset?=?bus->p->drivers_kset;
error?=?kobject_init_and_add(&priv->kobj,?&driver_ktype,?NULL,
"%s",?drv->name);
if?(error)
goto?out_unregister;
klist_add_tail(&priv->knode_bus,?&bus->p->klist_drivers);
if?(drv->bus->p->drivers_autoprobe)?{?如果設置了自動探測
error?=?driver_attach(drv);
if?(error)
goto?out_unregister;
}
module_add_driver(drv->owner,?drv);
error?=?driver_create_file(drv,?&driver_attr_uevent);
if?(error)?{
printk(KERN_ERR?"%s:?uevent?attr?(%s)?failedn",
__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)?failedn",
__func__,?drv->name);
}
if?(!drv->suppress_bind_attrs)?{
error?=?add_bind_files(drv);
if?(error)?{
/*?Ditto?*/
printk(KERN_ERR?"%s:?add_bind_files(%s)?failedn",
__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會嘗試綁定設備和驅(qū)動。編譯總線上的所有設備,然驅(qū)動挨個嘗試匹配,如果driver_probe_device()返回0且@dev->driver被設置,就代表找到了一對兼容的設備驅(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
對于每一個總線的設備,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))??匹配設備和驅(qū)動,這里調(diào)用的是platform_match
return?0;
if?(dev->parent)?/*?Needed?for?USB?*/
device_lock(dev->parent);
device_lock(dev);??設置互斥鎖,防止其他進程訪問設備資源
if?(!dev->driver)
如果設備沒有驅(qū)動,則為設備探測驅(qū)動,這個函數(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,也就是說設備和驅(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總線設備,而platform總線的定義參考3.2.6 platform_bus_type。Platform對應的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í)行流程如下:
所以重點應該在__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. 使用設備資源
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()的’id’參數(shù),以便多個initcall可以指向同一個處理程序,而不會導致重復符號構建錯誤。若不理解上述代碼的用法,可以參考__attribute__的section用法和C語言宏定義中#和##的用法。所以將__define_initcall展開將會是下面的內(nèi)容:
假設__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)核啟動流程為:
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展開
如果設置為編譯成動態(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)用對應內(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ù)結構
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. '#'和'##'的一些應用特例
合并匿名變量名
#define??___ANONYMOUS1(type,?var,?line)??type??var##line
#define??__ANONYMOUS0(type,?line)??___ANONYMOUS1(type,?_anonymous,?line)
#define??ANONYMOUS(type)??__ANONYMOUS0(type,?__LINE__)
例:ANONYMOUS(static int); ?即 static int _anonymous70; ?70表示該行行號;第一層:ANONYMOUS(static int); --> ?__ANONYMOUS0(static int, LINE);
第二層:? ? ? ? ? ? ? ? ? ? ? ? ? ?--> ?___ANONYMOUS1(static int, _anonymous, 70);
第三層:? ? ? ? ? ? ? ? ? ? ? ? ? ?--> ?static int ?_anonymous70;
即每次只能解開當前層的宏,所以__LINE__在第二層才能被解開;
填充結構
#define??FILL(a)???{a,?#a}
enum?IDD{OPEN,?CLOSE};
typedef?struct?MSG{
IDD?id;
const?char??msg;
}MSG;
MSG?_msg[]?=?{FILL(OPEN),?FILL(CLOSE)};
相當于:
MSG?_msg[]?=?{{OPEN,?OPEN},
{CLOSE,?CLOSE}};
記錄文件名
#define??_GET_FILE_NAME(f)???#f
#define??GET_FILE_NAME(f)????_GET_FILE_NAME(f)
static?char??FILE_NAME[]?=?GET_FILE_NAME(__FILE__);
得到一個數(shù)值類型所對應的字符串緩沖大小
#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];