農(nóng)八師建設(shè)兵團(tuán)社保網(wǎng)站保定seo網(wǎng)站推廣
設(shè)備樹開發(fā)詳解
設(shè)備樹概念
Device Tree是一種描述硬件的數(shù)據(jù)結(jié)構(gòu),以便于操作系統(tǒng)的內(nèi)核可以管理和使用這些硬件,包括CPU或CPU,內(nèi)存,總線和其他一些外設(shè)。
Linux內(nèi)核從3.x版本之后開始支持使用設(shè)備樹,可以實(shí)現(xiàn)驅(qū)動(dòng)代碼與設(shè)備的硬件信息相互的隔離,減少了代碼中的耦合性
-
引入設(shè)備樹之前:一些與硬件設(shè)備相關(guān)的具體信息都要寫在驅(qū)動(dòng)代碼中,如果外設(shè)發(fā)生相應(yīng)的變化,那么驅(qū)動(dòng)代碼就需要改動(dòng)。
-
引入設(shè)備樹之后:通過設(shè)備樹對(duì)硬件信息的抽象,驅(qū)動(dòng)代碼只要負(fù)責(zé)處理邏輯,而關(guān)于設(shè)備的具體信息存放到設(shè)備樹文件中。如果只是硬件接口信息的變化而沒有驅(qū)動(dòng)邏輯的變化,開發(fā)者只需要修改設(shè)備樹文件信息,不需要改寫驅(qū)動(dòng)代碼。
一、DTS、DTB和DTC
- DTS
- 設(shè)備樹源碼文件,硬件的相應(yīng)信息都會(huì)寫在.dts為后綴的文件中,每一款硬件可以單獨(dú)寫一份xxxx.dts
- DTSI
- 對(duì)于一些相同的dts配置可以抽象到dtsi文件中,然后可以用include的方式到dts文件中
- 同一芯片可以做一個(gè)dtsi,不同的板子不同的dts,然后include同一dtsi
- 對(duì)于同一個(gè)節(jié)點(diǎn)的設(shè)置情況,dts中的配置會(huì)覆蓋dtsi中的配置
- DTC
- dtc是編譯dts的工具
- DTB
- dts經(jīng)過dtc編譯之后會(huì)得到dtb文件,設(shè)備樹的二進(jìn)制執(zhí)行文件
- dtb通過Bootloader引導(dǎo)程序加載到內(nèi)核。
二、設(shè)備樹框架
1.根節(jié)點(diǎn):\2.設(shè)備節(jié)點(diǎn):nodex①節(jié)點(diǎn)名稱:node②節(jié)點(diǎn)地址:node@0, @后面即為地址3.屬性:屬性名稱(Property name)和屬性值(Property value)4.標(biāo)簽
- “/”是根節(jié)點(diǎn),每個(gè)設(shè)備樹文件只有一個(gè)根節(jié)點(diǎn)。在設(shè)備樹文件中會(huì)發(fā)現(xiàn)有的文件下也有“/”根節(jié)點(diǎn),這兩個(gè)**“/”根節(jié)點(diǎn)的內(nèi)容會(huì)合并成一個(gè)根節(jié)點(diǎn)。**
- Linux 內(nèi)核啟動(dòng)的時(shí)會(huì)解析設(shè)備樹中各個(gè)節(jié)點(diǎn)的信息,并且在根文件系統(tǒng)的/proc/devicetree 目錄下根據(jù)節(jié)點(diǎn)名字創(chuàng)建不同文件夾
三、DTS語法
3.1 dtsi頭文件
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
設(shè)備樹也支持頭文件,設(shè)備樹的頭文件擴(kuò)展名為.dtsi。在.dts 設(shè)備樹文件中,還可以通過“#include”來引用.h、 .dtsi 和.dts 文件。
3.2 設(shè)備節(jié)點(diǎn)
-
設(shè)備樹是采用樹形結(jié)構(gòu)來描述板子上的設(shè)備信息的文件,每個(gè)設(shè)備都是一個(gè)節(jié)點(diǎn),叫做設(shè)備節(jié)點(diǎn),
-
每個(gè)節(jié)點(diǎn)都通過一些屬性信息來描述節(jié)點(diǎn)信息,屬性就是鍵—值對(duì)。
label: node-name@unit-address label:節(jié)點(diǎn)標(biāo)簽,方便訪問節(jié)點(diǎn):通過&label訪問節(jié)點(diǎn),追加節(jié)點(diǎn)信息 node-name:節(jié)點(diǎn)名字,為字符串,描述節(jié)點(diǎn)功能 unit-address:設(shè)備的地址或寄存器首地址,若某個(gè)節(jié)點(diǎn)沒有地址或者寄存器,可以省略
-
設(shè)備樹源碼中常用的幾種數(shù)據(jù)形式
1.字符串: compatible = "arm,cortex-a7";設(shè)置 compatible 屬性的值為字符串“arm,cortex-a7” 2.32位無符號(hào)整數(shù):reg = <0>; 設(shè)置reg屬性的值為0 3.字符串列表:字符串和字符串之間采用“,”隔開 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand"; 設(shè)置屬性 compatible 的值為“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。
3.3 屬性
-
compatible屬性(兼容屬性)
"manufacturer,model" manufacturer:廠商名稱 model:模塊對(duì)應(yīng)的驅(qū)動(dòng)名字
例:
imx6ull-alientekemmc.dts 中 sound 節(jié)點(diǎn)是 音頻設(shè)備節(jié)點(diǎn),采用的歐勝(WOLFSON)出品的 WM8960, sound 節(jié)點(diǎn)的 compatible 屬性值如下:compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
-
屬性值有兩個(gè),分別為“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl”表示廠商是飛思卡爾,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驅(qū)動(dòng)模塊名字。
-
sound這個(gè)設(shè)備首先使用第一個(gè)兼容值在 Linux 內(nèi)核里面查找,看看能不能找到與之匹配的驅(qū)動(dòng)文件,如果沒有找到的話就使用第二個(gè)兼容值查。
-
一般驅(qū)動(dòng)程序文件會(huì)有一個(gè) OF 匹配表,此 OF 匹配表保存著一些 compatible 值,如果設(shè)備節(jié)點(diǎn)的 compatible 屬性值和 OF 匹配表中的任何一個(gè)值相等,那么就表示設(shè)備可以使用這個(gè)驅(qū)動(dòng)。
-
在根節(jié)點(diǎn)來說,Linux 內(nèi)核會(huì)通過根節(jié)點(diǎn)的 compoatible 屬性查看是否支持此設(shè)備,如果支持的話設(shè)備就會(huì)啟動(dòng) Linux 內(nèi)核。如果不支持的話那么這個(gè)設(shè)備就沒法啟動(dòng) Linux 內(nèi)核。
-
-
model屬性
model 屬性值是一個(gè)字符串,一般 model 屬性描述設(shè)備模塊信息。 -
status屬性
status 屬性和設(shè)備狀態(tài)有關(guān)的, status 屬性值是字符串,描述設(shè)備的狀態(tài)信息。
-
#address-cells 和#size-cells 屬性
用于描述子節(jié)點(diǎn)的地址信息,reg屬性的address 和 length的字長(zhǎng)。
- #address-cells 屬性值決定了子節(jié)點(diǎn) reg 屬性中地址信息所占用的字長(zhǎng)(32 位),
- #size-cells 屬性值決定了子節(jié)點(diǎn) reg 屬性中長(zhǎng)度信息所占的字長(zhǎng)(32 位)。
- 子節(jié)點(diǎn)的地址信息描述來自于父節(jié)點(diǎn)的#address-cells 和#size-cells的值,而不是該節(jié)點(diǎn)本身的值(當(dāng)前節(jié)點(diǎn)的信息是描述子節(jié)點(diǎn)的,自己的信息在父節(jié)點(diǎn)里)
//每個(gè)“address length”組合表示一個(gè)地址范圍, //其中 address 是起始地址, length 是地址長(zhǎng)度, //#address-cells 表明 address 這個(gè)數(shù)據(jù)所占用的字長(zhǎng), // #size-cells 表明 length 這個(gè)數(shù)據(jù)所占用的字長(zhǎng). reg = <address1 length1 address2 length2 address3 length3……>
-
reg屬性
reg 屬性一般用于描述設(shè)備地址空間資源信息,一般都是某個(gè)外設(shè)的寄存器地址范圍信息, reg 屬性的值一般是(address, length)對(duì).例
uart1: serial@02020000 {compatible = "fsl,imx6ul-uart","fsl,imx6q-uart", "fsl,imx21-uart";reg = <0x02020000 0x4000>;interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_UART1_IPG>,<&clks IMX6UL_CLK_UART1_SERIAL>;clock-names = "ipg", "per";status = "disabled"; };
uart1 的父節(jié)點(diǎn) aips1: aips-bus@02000000 設(shè)置了#address-cells = <1>、 #sizecells = <1>,因此 reg 屬性中 address=0x02020000, length=0x4000。都是字長(zhǎng)為1.
-
ranges屬性
-
ranges屬性值可以為空或者按照( child-bus-address , parent-bus-address , length )格式編寫的數(shù)字
-
ranges 是一個(gè)地址映射/轉(zhuǎn)換表, ranges 屬性每個(gè)項(xiàng)目由子地址、父地址和地址空間長(zhǎng)度這三部分組成。
-
如果 ranges 屬性值為空值,說明子地址空間和父地址空間完全相同,不需要進(jìn)行地址轉(zhuǎn)換。
child-bus-address:子總線地址空間的物理地址,由父節(jié)點(diǎn)的#address-cells 確定此物理地址所占用的字長(zhǎng) parent-bus-address: 父總線地址空間的物理地址,同樣由父節(jié)點(diǎn)的#address-cells 確定此物理地址所占用的字長(zhǎng) length: 子地址空間的長(zhǎng)度,由父節(jié)點(diǎn)的#size-cells 確定此地址長(zhǎng)度所占用的字長(zhǎng)
-
-
特殊節(jié)點(diǎn)
在根節(jié)點(diǎn)“/”中有兩個(gè)特殊的子節(jié)點(diǎn): aliases 和 chosen
-
aliases
aliases {can0 = &flexcan1;can1 = &flexcan2;...usbphy0 = &usbphy1;usbphy1 = &usbphy2; };
aliases 節(jié)點(diǎn)的主要功能就是定義別名,定義別名的目的就是為了方便訪問節(jié)點(diǎn)。
但是,一般會(huì)在節(jié)點(diǎn)命名的時(shí)候會(huì)加上 label,然后通過&label來訪問節(jié)點(diǎn)。
-
chosen
chosen 不是一個(gè)真實(shí)的設(shè)備, chosen 節(jié)點(diǎn)主要是為了 uboot 向 Linux 內(nèi)核傳遞數(shù)據(jù)(bootargs 參數(shù))。
-
四、OF操作函數(shù)
Linux 內(nèi)核提供了一系列的函數(shù)來獲取設(shè)備樹中的節(jié)點(diǎn)或者屬性信息,這一系列的函數(shù)都有一個(gè)統(tǒng)一的前綴“of_” (稱為OF 函數(shù))
4.1 查找節(jié)點(diǎn)
Linux 內(nèi)核使用 device_node 結(jié)構(gòu)體來描述一個(gè)節(jié)點(diǎn):
struct device_node {const char *name; /* 節(jié)點(diǎn)名字 */const char *type; /* 設(shè)備類型 */phandle phandle;const char *full_name; /* 節(jié)點(diǎn)全名 */struct fwnode_handle fwnode;struct property *properties; /* 屬性 */struct property *deadprops; /* removed 屬性 */struct device_node *parent; /* 父節(jié)點(diǎn) */struct device_node *child; /* 子節(jié)點(diǎn)...
}
-
通過節(jié)點(diǎn)名字查找指定的節(jié)點(diǎn):of_find_node_by_name
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
from:開始查找的節(jié)點(diǎn),如果為 NULL 表示從根節(jié)點(diǎn)開始查找整個(gè)設(shè)備樹。
name:要查找的節(jié)點(diǎn)名字。
返回值: 找到的節(jié)點(diǎn),如果為 NULL 表示查找失敗。 -
通過 device_type 屬性查找指定的節(jié)點(diǎn):of_find_node_by_type
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
from:開始查找的節(jié)點(diǎn),如果為 NULL 表示從根節(jié)點(diǎn)開始查找整個(gè)設(shè)備樹。
type:要查找的節(jié)點(diǎn)對(duì)應(yīng)的 type 字符串, device_type 屬性值。
返回值: 找到的節(jié)點(diǎn),如果為 NULL 表示查找失敗 -
通過device_type 和 compatible兩個(gè)屬性查找指定的節(jié)點(diǎn):of_find_compatible_node
struct device_node *of_find_compatible_node(struct device_node *from,const char *type,const char *compatible)
from:開始查找的節(jié)點(diǎn),如果為 NULL 表示從根節(jié)點(diǎn)開始查找整個(gè)設(shè)備樹。
type:要查找的節(jié)點(diǎn)對(duì)應(yīng)的 type 字符串,device_type 屬性值,可以為 NULL
compatible: 要查找的節(jié)點(diǎn)所對(duì)應(yīng)的 compatible 屬性列表。
返回值: 找到的節(jié)點(diǎn),如果為 NULL 表示查找失敗 -
通過 of_device_id 匹配表來查找指定的節(jié)點(diǎn):of_find_matching_node_and_match
struct device_node *of_find_matching_node_and_match(struct device_node *from,const struct of_device_id *matches,const struct of_device_id **match)
from:開始查找的節(jié)點(diǎn),如果為 NULL 表示從根節(jié)點(diǎn)開始查找整個(gè)設(shè)備樹。
matches: of_device_id 匹配表,在此匹配表里面查找節(jié)點(diǎn)。
match: 找到的匹配的 of_device_id。
返回值: 找到的節(jié)點(diǎn),如果為 NULL 表示查找失敗 -
通過路徑來查找指定的節(jié)點(diǎn):of_find_node_by_path
inline struct device_node *of_find_node_by_path(const char *path)
path:設(shè)備樹節(jié)點(diǎn)中絕對(duì)路徑的節(jié)點(diǎn)名,可以使用節(jié)點(diǎn)的別名
返回值: 找到的節(jié)點(diǎn),如果為 NULL 表示查找失敗
4.2 獲取屬性值
Linux 內(nèi)核中使用結(jié)構(gòu)體 property 表示屬性
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;
}
-
查找指定的屬性:of_find_property
property *of_find_property(const struct device_node *np,const char *name,int *lenp)
np:設(shè)備節(jié)點(diǎn)。
name: 屬性名字。
lenp:屬性值的字節(jié)數(shù),一般為NULL
返回值: 找到的屬性。 -
獲取屬性中元素的數(shù)量(數(shù)組):of_property_count_elems_of_size
int of_property_count_elems_of_size(const struct device_node *np,const char *propnameint elem_size)
np:設(shè)備節(jié)點(diǎn)。
proname: 需要統(tǒng)計(jì)元素?cái)?shù)量的屬性名字。
elem_size:元素長(zhǎng)度。
返回值: 得到的屬性元素?cái)?shù)量 -
從屬性中獲取指定標(biāo)號(hào)的 u32 類型數(shù)據(jù)值:of_property_read_u32_index
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index,u32 *out_value)
np:設(shè)備節(jié)點(diǎn)。
proname: 要讀取的屬性名字。
index:要讀取的值標(biāo)號(hào)。
out_value:讀取到的值
返回值: 0 讀取成功;
負(fù)值: 讀取失敗,
-EINVAL 表示屬性不存在
-ENODATA 表示沒有要讀取的數(shù)據(jù),
-EOVERFLOW 表示屬性值列表太小 -
讀取屬性中 u8、 u16、 u32 和 u64 類型的數(shù)組數(shù)據(jù)
of_property_read_u8_array of_property_read_u16_array of_property_read_u32_array of_property_read_u64_array int of_property_read_u8_array(const struct device_node *np,const char *propname,u8 *out_values,size_t sz)
np:設(shè)備節(jié)點(diǎn)。
proname: 要讀取的屬性名字。
out_value:讀取到的數(shù)組值,分別為 u8、 u16、 u32 和 u64。
sz: 要讀取的數(shù)組元素?cái)?shù)量。
返回值: 0:讀取成功;
負(fù)值: 讀取失敗
-EINVAL 表示屬性不存在
-ENODATA 表示沒有要讀取的數(shù)據(jù)
-EOVERFLOW 表示屬性值列表太小 -
讀取屬性中字符串值:of_property_read_string
int of_property_read_string(struct device_node *np,const char *propname,const char **out_string)
np:設(shè)備節(jié)點(diǎn)。
proname: 要讀取的屬性名字。
out_string:讀取到的字符串值。
返回值: 0,讀取成功,負(fù)值,讀取失敗 -
獲取 #address-cells 屬性值:of_n_addr_cells ,獲取 #size-cells 屬性值:of_size_cells 。
int of_n_addr_cells(struct device_node *np) int of_n_size_cells(struct device_node *np)
np:設(shè)備節(jié)點(diǎn)。
返回值: 獲取到的#address-cells 屬性值。
返回值: 獲取到的#size-cells 屬性值。 -
內(nèi)存映射
of_iomap 函數(shù)用于直接內(nèi)存映射,前面通過 ioremap 函數(shù)來完成物理地址到虛擬地址的映射,采用設(shè)備樹以后就可以直接通過 of_iomap 函數(shù)來獲取內(nèi)存地址所對(duì)應(yīng)的虛擬地址。這樣就不用再去先獲取reg屬性值,再用屬性值映射內(nèi)存。of_iomap 函數(shù)本質(zhì)上也是將 reg 屬性中地址信息轉(zhuǎn)換為虛擬地址,如果 reg 屬性有多段的話,可以通過 index 參數(shù)指定要完成內(nèi)存映射的是哪一段, of_iomap 函數(shù)原型如下:
void __iomem *of_iomap(struct device_node *np, int index)
np:設(shè)備節(jié)點(diǎn)。
index: reg 屬性中要完成內(nèi)存映射的段,如果 reg 屬性只有一段的話 index 就設(shè)置為 0。
返回值: 經(jīng)過內(nèi)存映射后的虛擬內(nèi)存首地址,如果為 NULL 的話表示內(nèi)存映射失敗。例
#if 1/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);GPIO1_DR = ioremap(regdata[6], regdata[7]);GPIO1_GDIR = ioremap(regdata[8], regdata[9]); #else //第一對(duì):起始地址+大小 -->映射 這樣就不用獲取reg的值IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0); SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);GPIO1_DR = of_iomap(dtsled.nd, 3);GPIO1_GDIR = of_iomap(dtsled.nd, 4); #endif