文章目錄
- 1 設備樹機制
- 命名約定
- 別名、標簽和phandle
- DT編譯器
- 2 表示和尋址設備
- SPI和I2C尋址
- 平臺設備尋址
- 3 處理資源
- 提取特定應用數據
- 文本字符串
- 單元格和無符號的32位整數
- 布爾
- 提取并分析子節點
- 4 平臺驅動程序與DT
- OF匹配風格
- 處理非設備樹平臺
- 平臺數據與DT
設備樹(DT)是易于閱讀的硬件描述文件,它采用JSON式的格式化風格,在這種簡單的樹形結構中, 設備表示為帶有屬性的節點。屬性可以為空(只有鍵,用來描述布爾值),也可以是鍵值對,其中的值可以是任意的字節流。
1 設備樹機制
將選項CONFIG_OF設置為Y即可在內核中啟用DT。要在驅動程序中調用DT API,必須添加以下頭文件
#include <linux/of.h>
#include <linux/of_device.h>
DT支持一些數據類型:
- 文本字符串用雙引號表示。可以使用逗號來創建字符串列表
- 單元格是由尖括號分隔的無符號32位整數
- 布爾類型不過是空屬性,其值是true或false取決于屬性存在于否
比如:
node_lable:nodename@reg{srting-property="a string"; //一個字符串string-list="read","write"; //字符串列表onw-int-property=<197>; //一個整型數字int-list-property=<0xbeef 123 0xabcd 4>; //整型列表byte-array-property=[0x01 0x02 0x35 0x47]; //字節數組bool-property; //布爾數據mixed-list-property="a string",<oxabcd 45>,<35>,[0x01 0x23 0x45]; //混合類型
};
命名約定
每個節點都必須由<name>[@<address>]形式的名稱,其中<name>是一個字符串,其最多可以為31個字符,[@<address>]是可選的,具體取決于節點代表的設備是否為可尋址的。<address>是用來訪問設備的主要地址。設備命名的一個例子如下:
i2c@021a0000{compatible = "fls,imx6q-i2c","fls,ima21-i2c";reg = <0x021a0000 0x4000>;...
}
別名、標簽和phandle
node_lable:nodename@reg{...
};gpio1:gpio@0209c000{...
};
aliases{...gpio1 = &gpio1;...
};
標簽不過是標記節點的方法,可以用唯一的名稱來標識節點。DT編譯器將該名稱轉換為唯一的32位值。在上面的例子中,gpio1和node_label都是標簽。之后可以用標簽來引用節點,因為標簽對于節點是唯一的。
指針句柄(point handle,簡寫為phandle)是與節點相關聯的32位值,用于唯一標識該結點,以便可以從另一個結點的屬性引用該節點。標簽用于一個指向節點的指針,使用&mylabel可以指向標簽為mylabel的節點,比如gpio1 = &gpio1
。&gpio1被轉換為phandle,以便引用gpio1節點。
為了在查找節點時不遍歷整棵樹,引入了別名的概念。在DT中,別名節點可以看做是快速查找表,可以使用函數find_node_by_alias()來查找指定別名的節點。別名不是直接在DT源中使用,而是由Linux內核來引用。
DT編譯器
DT有兩種形式:文本形式(代表源,也稱作DTS)和二進制塊形式(代表編譯后的DT),也稱作DTB。源文件的拓展名是.dts。.dtsi是SOC級定義,.dts文件代表開發板定義。就像源文件(.c)和包含頭文件(.h)一樣,應該把.dtsi作為頭文件包含在.dts文件中。而二進制文件使用.dtb拓展名。
2 表示和尋址設備
每個設備在DT中至少有一個節點。某些屬性對于設備是通用的,這些屬性是reg、#address-cells和#size-cells,它們的用途是在其所在總線上進行設備尋址。主要的尋址屬性是reg,其含義取決于設備所在的總線。size-cells和address-cells的前綴#可以翻譯為length。
每個可尋址設備都具有reg屬性,該屬性是reg = <address0 size0>,<address1 size1>…形式的元組列表,其中每個元組代表設備的地址范圍。#size-cells(長度單元)指示使用多少個32位單元來表示size0,如果與大小無關,則可以是0。 #address-cells(地址單元)指示用多少個32位單元來表示address0 。
可尋址設備繼承它們父節點的#size-cells和#address-cells,其指定的#size-cells和#address-cells影響子設備。
SPI和I2C尋址
SPI和I2C設備都屬于非內存映射設備,因為它們的地址對CPU不可訪問。而總線控制器驅動程序將代表CPU執行間接訪問。每個I2C/SPI設備都表示為在I2C/SPI總線節點上的子節點。對于非存儲映射的設備,#size-cells表示為0,尋址元組中的size元素為空。這意味著這種設備的reg屬性總是只有一個單元:
&i2c3{...temperature-sensor@49{...reg=<0x49>;...};pcf8523:rtc@68{...reg = <0x68>;...};...
};&ecspi{...cs-gpios = <&gpio4 17 0>,<&gpio5 17 0>,<&gpio6 17 0>;...ad7606r8_0:ad7606r8@1{...reg = <1>;...};...
};
reg屬性只是一個保存地址值的單元格,I2C設備的reg屬性用于指定總線上設備的地址。對于SPI,reg表示從控制器節點 所具有的芯片選擇列表中 分配給設備的芯片選擇線的索引。例如對于ad7606r8 ADC,芯片選擇索引是1,對應于cs-gpios中的<&gpio5 17 0>,cs-gpios是控制器節點的芯片選擇列表。
平臺設備尋址
下面介紹的內存映射設備,其內存可由CPU訪問。在這里,reg屬性仍然定義設備的地址,這是可以訪問設備的內存區域列表。每個區域用單元格元組表示,其中第一個單元格是內存區域的基地址,第二個單元格是該區域的大小。每個元組代表設備使用的地址范圍。
這種設備應該在具有特殊值compatible= "simple-bus"的節點內聲明,這意味著簡單的內存映射總線,沒有特定的處理和驅動程序:
soc{#address-cells = <1>;#size-cells = <1>;compatible = "simple-bus";aips-bus@02000000{...#address-cells = <1>;#size-cells = <1>;reg = <0x02000000 0x100000>;...spba-bus@02000000{...#address-cells = <1>;#size-cells = <1>;reg = <0x02000000 0x400000>;...ecspi:ecspi@02008000{...#address-cells = <1>;#size-cells = <0>;reg = <0x02008000 0x400>;...};...};...};...
};
父結點compatible= “simple-bus”,其在節點將被注冊為平臺設備。設置#size-cells=<0>也能夠看到SPI總線控制器怎樣改變其子節點的尋址方式的。從內核設備樹文檔可以查找所有 綁定信息:Document/devicetree/binding/。
3 處理資源
當驅動程序期望某種類型的資源列表時,由于編寫開發板設備樹的人通常不是寫驅動程序的人,因此不能保證該列表是以驅動程序期望的方式排序。例如,驅動程序可能期望其設備節點具有2條IRQ線路,一條用于索引0處的Tx事件,另一條用于索引1處的Rx。如果這種順序得不到滿足,驅動就會發生異常行為。為了避免這種不匹配,引入了命名資源的概念,它由定義資源列表和命名組成,因此無論索引什么,給定的名稱總將與資源相匹配。
命名資源的相關屬性如下:
- reg-names:reg屬性中的內存區域列表
- clock-names:clocks屬性中命名clocks
- interrupt-names:為interrupts屬性中的每個中斷指定一個名稱
- dma-names:用于dma屬性
例如:
fake_device{...reg=<0x4a06400 0x800>,<0x4a064800 0x200>,<0x4a064c00 0x200>;reg-name="config","ohci","ehci";interrupters=<0 66 IRQ_TYPE_LEVEL_HIGH>,<0 67 IRQ_TYPE_LEVEL_HIGH>;interrupter-names="ohci","ehci";clocks=<&clks IMAX6QDL_CLK_UART_IPG>,<&clks IMAX6QDL_CLK_UART_SERTAL>;clock-names="ipg","per";dmas=<&sdma 25 4 0>,<&sdma 26 4 0>;dma-names="rx","tx";
};
驅動程序中提取每個命名資源代碼如下所示:
struct resource *res1,*res2;
res1 = platform_get_resource_byname(pdev,IORESOURCE_MEM,"ohci");
res2 = platform_get_resource_byname(pdev,IORESOURCE_MEM,"config");struct dma_chan *dma_chan_rx,*dma_chan_tx;
dma_chan_rx=dma_request_slave_channel(&pdev->dev,"rx");
dma_chan_tx=dma_request_slave_channel(&pdev->dev,"tx");int txirq,rxirq;
txirq = platform_get_irq_byname(pdev,"ohci");
rxirq = platform_get_irq_byname(pdev,"ehci");struct *clk_ipg,*clk_per;
clk_ipg = devm_clk_get(&pdev->dev,"ipg");
clk_per = devm_clk_get(&pdev->dev,"per");
這樣,就可以確保把正確的名字映射到正確的資源上,而不用再使用索引了。
提取特定應用數據
特定應用數據時公共屬性之外的數據(既不是資源,也不是IO、調度器等),可以分配給設備的任意屬性和子節點,這樣的屬性名稱通常以制造商代碼作前綴。它們可以是任何字符串、布爾值或整型值。
文本字符串
下面是一個string屬性:
string-property = "a string";
驅動程序使用of_property_read_string()讀取字符串值,其原型定義如下:
int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string);
以下代碼說明如何使用它:
const char *my_string = NULL;
of_property_read_string(pdev->dev.of_node,"string-property",&my_string);
單元格和無符號的32位整數
下面是unsigned int屬性:
one-int-property=<197>;
int-list-property = <1350000 0x54dae47 1250000 1200000>;
應該使用of_property_read_u32()讀取單元格值。其原型定義如下:
int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value);
驅動中使用:
unsigned int number;
of_property_read_u32(pdev->dev.of_node,"one-cell-property",&number);
可以使用of_property_read_u32_array()讀取單元格列表,其原型如下:
int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_values, size_t sz);
sz是要讀取數組元素的數量。比如:
unsigned int cells_array[4];
of_property_read_u32_array(pdev->dev.of_node,"int-list-property",cells_array,4);
布爾
使用of_property_read_bool()讀取布爾屬性。屬性的名稱在函數的第二個參數中給出:
bool my_bool = of_property_read_bool(pdev->dev.of_node,"bool-property");
if(my_bool)
{/* 布爾值為真 */
}
else{/* 布爾值為假 */
}
提取并分析子節點
使用for_each_child_of_node()遍歷指定節點的子節點:
struct device_node *np = pdev->dev.of_node;
struct device_node *sub_np;
for_each_child_of_node(np,sub_np)
{...
}
4 平臺驅動程序與DT
平臺驅動程序也使用DT,也就是說,這是現在推薦的處理平臺設備的方法,不在需要使用開發板文件,甚至不需要在設備的屬性更改時重新編譯內核。
OF匹配風格
OF匹配風格是平臺核心執行的第一種匹配機制,以匹配設備及其驅動程序。它使用設備樹的compatible屬性來匹配of_match_table中的設備項,設備項是struct driver
子結構的一個字段。每個設備節點都有compatible屬性,它是字符串或字符串列表。任何驅動程序只要聲明compatible屬性中列出的字符串之一,就將觸發匹配,并看到probe函數執行。
DT匹配項在內核中被描述為struct of_device_id
結構的實例,該結構定義在liunx/mod_devicetable.h中,如下所示:
struct of_device_id{...char compatible[128];const void *data;
};
- compatible:這是用來匹配DT設備節點兼容屬性的字符串字符串,它們必須完全相同才可以匹配
- data:這可以指向任何結構,這個結構可以用作每個設備類型的配置數據。
of_match_table是指針,可以傳遞struct of_device_id數組,使驅動程序兼容多個設備:
static const struct of_device_id imx_uart_dt_ids[] = {{ .compatible = "fsl,imx6q-uart",},{ .compatible = "fsl,imx1-uart",},{ .compatible = "fsl,imx21-uart",},....
};
填充了of_device_id 數組,就必須把它傳遞到平臺驅動程序的of_match_table字段:
static struct platform_driver serial_imx_driver = {....driver = {.name = "imax-uart",.of_match_table = imx_uart_dt_ids,...};
};
這一步,只有驅動程序知道of_device_id數組,要使內核也或得通知(這樣可以把of_device_id 數組存儲到平臺內核維護的設備列表中),該數組必須用MODULE_DEVICE_TABLE(設備類型,設備表)
注冊:
MODULE_DEVICE_TABLE(of,imx_uart_dt_ids);
這樣,驅動程序就兼容DT,接下來聲明與驅動程序兼容的設備:
uart1:serial@02020000{compatible = "fsl,imx6q-uart","fls,imx21-uart";reg = <0x02020000 0x4000>;...
};
這里提供了兩個兼容的字符串,如果第一個與任何驅動程序都不匹配,則平臺核心將執行第二個匹配。
當發生匹配時,將以struct platform_device 結構作為參數調用驅動程序的probe函數。
處理非設備樹平臺
使用CONFIG_OF選項在內核中啟用DT支持。如果內核沒有啟用DT支持,則可能會希望避免使用DT API。其實現的方法是檢查CONFIG_OF是否設置。過去常常這樣做:
#ifdef CONFIG_OFstatic const struct of_device_id imx_uart_dt_ids[] = {{ .compatible = "fsl,imx6q-uart",},{ .compatible = "fsl,imx1-uart",},{ .compatible = "fsl,imx21-uart",},....};....
#endif
這不是唯一的選擇,還可以使用of_match_ptr宏,當OF被禁用時它簡單地返回NULL,查看源代碼,在include/linux/of.h里面。
#ifdef CONFIG_OF
...
#define of_match_ptr(_ptr) (_ptr)
...
#else /* CONFIG_OF */
...
#define of_match_ptr(_ptr) NULL
...
#endif /* CONFIG_OF */
平臺數據與DT
如果驅動程序需要平臺數據,則應該檢查dev.platfrom_data指針。非空值表示驅動程序已經在開發板配置文件中以舊的方法實例化,并且DT不會處理它。對于從DT實例化的驅動程序,dev.platform_data將為NULL,平臺設備將在DT項上獲得一個指針,該指針對應于dev.of_node指針中的設備,可以從該指針中提取資源并使用OF API分析和提取應用數據