關(guān)與設(shè)備樹的概念,我們在Exynos4412 內(nèi)核移植(六)—— 設(shè)備樹解析?里面已經(jīng)學(xué)習(xí)過,下面看一下設(shè)備樹在設(shè)備驅(qū)動開發(fā)中起到的作用
? ? ? ???Device Tree是一種描述硬件的數(shù)據(jù)結(jié)構(gòu),設(shè)備樹源(Device Tree Source)文件(以.dts結(jié)尾)就是用來描述目標(biāo)板硬件信息的。Device Tree由一系列被命名的結(jié)點(node)和屬性(property)組成,而結(jié)點本身可包含子結(jié)點。所謂屬性,其實就是成對出現(xiàn)的name和value。在Device Tree中,可描述的信息包括(原先這些信息大多被hard code到kernel中)。
一、設(shè)備樹基礎(chǔ)概念
1、基本數(shù)據(jù)格式
? ? ? device tree是一個簡單的節(jié)點和屬性樹,屬性是鍵值對,節(jié)點可以包含屬性和子節(jié)點。下面是一個.dts格式的簡單設(shè)備樹。
[cpp]?view plaincopy
/?{?? ????node1?{?? ????????a-string-property?=?"A?string";?? ????????a-string-list-property?=?"first?string",?"second?string";?? ????????a-byte-data-property?=?[0x01?0x23?0x34?0x56];?? ????????child-node1?{?? ????????????first-child-property;?? ????????????second-child-property?=?<1>;?? ????????????a-string-property?=?"Hello,?world";?? ????????};?? ????????child-node2?{?? ????????};?? ????};?? ????node2?{?? ????????an-empty-property;?? ????????a-cell-property?=?<1?2?3?4>;??? ????????child-node1?{?? ????????};?? ????};?? };??
? ? ? 該樹并未描述任何東西,也不具備任何實際意義,但它卻揭示了節(jié)點和屬性的結(jié)構(gòu)。即:
a -- 一個的根節(jié)點:'/',兩個子節(jié)點:node1和node2;node1的子節(jié)點:child-node1和child-node2,一些屬性分散在樹之間。
b -- 屬性是一些簡單的鍵值對(key-value pairs):value可以為空也可以包含任意的字節(jié)流。而數(shù)據(jù)類型并沒有編碼成數(shù)據(jù)結(jié)構(gòu),有一些基本數(shù)據(jù)表示可以在device tree源文件中表示。
c -- 文本字符串(null 終止)用雙引號來表示:string-property = "a string"
d -- “Cells”是由尖括號分隔的32位無符號整數(shù):cell-property = <0xbeef 123 0xabcd1234>
e -- 二進(jìn)制數(shù)據(jù)是用方括號分隔:binary-property = [0x01 0x23 0x45 0x67];
f -- 不同格式的數(shù)據(jù)可以用逗號連接在一起:mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
g -- 逗號也可以用來創(chuàng)建字符串列表:string-list = "red fish", "blue fish";
二、設(shè)備在device tree 中的描述
? ? ? ??系統(tǒng)中的每個設(shè)備由device tree的一個節(jié)點來表示;
1、節(jié)點命名
? ? ?花些時間談?wù)劽?xí)慣是值得的。每個節(jié)點都必須有一個<name>[@<unit-address>]格式的名稱。<name>是一個簡單的ascii字符串,最長為31個字符,總的來說,節(jié)點命名是根據(jù)它代表什么設(shè)備。比如說,一個代表3com以太網(wǎng)適配器的節(jié)點應(yīng)該命名為ethernet,而不是3com509。
? ? 如果節(jié)點描述的設(shè)備有地址的話,就應(yīng)該加上unit-address,unit-address通常是用來訪問設(shè)備的主地址,并在節(jié)點的reg屬性中被列出。后面我們將談到reg屬性。
2、設(shè)備
? ? ? 接下來將為設(shè)備樹添加設(shè)備節(jié)點:
[cpp]?view plaincopy
/?{?? ????compatible?=?"acme,coyotes-revenge";?? ?? ????cpus?{?? ????????cpu@0?{?? ????????????compatible?=?"arm,cortex-a9";?? ????????};?? ????????cpu@1?{?? ????????????????????compatible?=?"arm,cortex-a9";?? ????????????};?? ????????};?? ?? ????serial@101F0000?{?? ????????compatible?=?"arm,pl011";?? ????};?? ?? ????serial@101F2000?{?? ????????compatible?=?"arm,pl011";?? ????};?? ?? ????gpio@101F3000?{?? ????????compatible?=?"arm,pl061";?? ????};?? ?? ????interrupt-controller@10140000?{?? ????????compatible?=?"arm,pl190";?? ????};?? ?? ????spi@10115000?{?? ????????compatible?=?"arm,pl022";?? ????};?? ?????? ????external-bus?{?? ????????ethernet@0,0?{?? ????????????compatible?=?"smc,smc91c111";?? ????????};?? ?????? ????????i2c@1,0?{?? ????????????compatible?=?"acme,a1234-i2c-bus";?? ????????????rtc@58?{?? ????????????????compatible?=?"maxim,ds1338";?? ????????????};?? ????????????};?? ?? ????????flash@2,0?{?? ????????????compatible?=?"samsung,k8f1315ebm",?"cfi-flash";?? ?????????????};?? ?????};?? };??
? ? ? ? 在上面的設(shè)備樹中,系統(tǒng)中的設(shè)備節(jié)點已經(jīng)添加進(jìn)來,樹的層次結(jié)構(gòu)反映了設(shè)備如何連到系統(tǒng)中。外部總線上的設(shè)備就是外部總線節(jié)點的子節(jié)點,i2c設(shè)備是i2c總線控制節(jié)點的子節(jié)點。總的來說,層次結(jié)構(gòu)表現(xiàn)的是從CPU視角來看的系統(tǒng)視圖。在這里這棵樹是依然是無效的。它缺少關(guān)于設(shè)備之間的連接信息。稍后將添加這些數(shù)據(jù)。
? ? ? 設(shè)備樹中應(yīng)當(dāng)注意:每個設(shè)備節(jié)點有一個compatible屬性。flash節(jié)點的compatible屬性有兩個字符串。請閱讀下一節(jié)以了解更多內(nèi)容。 之前提到的,節(jié)點命名應(yīng)當(dāng)反映設(shè)備的類型,而不是特定型號。請參考ePAPR規(guī)范2.2.2節(jié)的通用節(jié)點命名,應(yīng)優(yōu)先使用這些命名。
3、compatible 屬性
? ? ? 樹中的每一個代表了一個設(shè)備的節(jié)點都要有一個compatible屬性。compatible是OS用來決定綁定到設(shè)備的設(shè)備驅(qū)動的關(guān)鍵。
? ? ? compatible是字符串的列表。列表中的第一個字符串指定了"<manufacturer>,<model>"格式的節(jié)點代表的確切設(shè)備,第二個字符串代表了與該設(shè)備兼容的其他設(shè)備。例如,Freescale MPC8349 SoC有一個串口設(shè)備實現(xiàn)了National Semiconductor ns16550寄存器接口。因此MPC8349串口設(shè)備的compatible屬性為:compatible = "fsl,mpc8349-uart", "ns16550"。在這里,fsl,mpc8349-uart指定了確切的設(shè)備,ns16550表明它與National?Semiconductor 16550 UART是寄存器級兼容的。
? ? ?注:由于歷史原因,ns16550沒有制造商前綴,所有新的compatible值都應(yīng)使用制造商的前綴。這種做法使得現(xiàn)有的設(shè)備驅(qū)動程序可以綁定到一個新設(shè)備上,同時仍能唯一準(zhǔn)確的識別硬件。
4、編址
? ? ? 可編址的設(shè)備使用下列屬性來將地址信息編碼進(jìn)設(shè)備樹:
reg
#address-cells
#size-cells
? ? ? ?每個可尋址的設(shè)備有一個reg屬性,即以下面形式表示的元組列表:
? ? ??reg = <address1 length1 [address2 length2] [address3 length3] ... >?
? ? ?每個元組,。每個地址值由一個或多個32位整數(shù)列表組成,被稱做cells。同樣地,長度值可以是cells列表,也可以為空。
? ? ?既然address和length字段是大小可變的變量,父節(jié)點的#address-cells和#size-cells屬性用來說明各個子節(jié)點有多少個cells。換句話說,正確解釋一個子節(jié)點的reg屬性需要父節(jié)點的#address-cells和#size-cells值。
5、內(nèi)存映射設(shè)備
? ? ? 與CPU節(jié)點中的單一地址值不同,內(nèi)存映射設(shè)備會被分配一個它能響應(yīng)的地址范圍。#size-cells用來說明每個子節(jié)點種reg元組的長度大小。
? ? ?在下面的示例中,每個地址值是1 cell (32位) ,并且每個的長度值也為1 cell,這在32位系統(tǒng)中是非常典型的。64位計算機(jī)可以在設(shè)備樹中使用2作為#address-cells和#size-cells的值來實現(xiàn)64位尋址。
[cpp]?view plaincopy
serial@101f2000?{?? ????compatible?=?"arm,pl011";?? ????reg?=?<0x101f2000?0x1000?>;?? };?? ?? gpio@101f3000?{?? ????????compatible?=?"arm,pl061";?? ????????reg?=?<0x101f3000?0x1000?? ???????????????0x101f4000?0x0010>;?? };?? ?? ?? interrupt-controller@10140000?{?? ????????compatible?=?"arm,pl190";?? ????????reg?=?<0x10140000?0x1000?>;?? };??
? ? ? 每個設(shè)備都被分配了一個基地址及該區(qū)域大小。本例中的GPIO設(shè)備地址被分成兩個地址范圍:0x101f3000~0x101f3fff和0x101f4000~0x101f400f。
三、設(shè)備樹在platform設(shè)備驅(qū)動開發(fā)中的使用解析
? ? ? ? ?我們?nèi)砸?Linux 設(shè)備驅(qū)動開發(fā) —— platform設(shè)備驅(qū)動應(yīng)用實例解析?文中的例子來解析設(shè)備樹在platform設(shè)備驅(qū)動中如何使用;
1、設(shè)備樹對platform中platform_device的替換
? ? ? ? ?其實我們可以看到,Device Tree 是用來描述設(shè)備信息的,每一個設(shè)備在設(shè)備樹中是以節(jié)點的形式表現(xiàn)出來;而在上面的 platform 設(shè)備中,我們利用platform_device 來描述一個設(shè)備,我們可以看一下二者的對比
fs4412-beep{ compatible = "fs4412,beep"; reg = <0x114000a0 0x4?0x139D0000 0x14>; }; a -- fs4412-beep 為節(jié)點名,符合咱們前面提到的節(jié)點命名規(guī)范; 我們通過名字可以知道,該節(jié)點描述的設(shè)備是beep, 設(shè)備名是fs4412-beep; b -- compatible = "fs4412,beep";?compatible 屬性, 即一個字符串; ? ? ??前面提到,所有新的compatible值都應(yīng)使用制造商的前綴,這里是 fs4412; c --?reg = <0x114000a0 0x4?0x139D0000 0x14>; ? ? ? ?reg屬性來將地址信息編碼進(jìn)設(shè)備樹,表示該設(shè)備的地址范圍;這里是我們用到的寄存器及偏移量; | static?struct??resource?beep_resource[]?= { [0]?=?{ .start?=?0x114000a0, .end?=?0x114000a0+0x4, .flags?=?IORESOURCE_MEM, }, [1]?=?{ .start?=?0x139D0000, .end?=?0x139D0000+0x14, .flags?=?IORESOURCE_MEM, }, }; static?struct?platform_device?hello_device= { .name?=?"bigbang",//沒用了 .id?=?-1, .dev.release?=?hello_release, .num_resources?=?ARRAY_SIZE(beep_resource?), .resource?=?beep_resource, }; |
? ? ? 可以看到設(shè)備樹中的設(shè)備節(jié)點完全可以替代掉platform_device。
2、有了設(shè)備樹,如何實現(xiàn)device 與 driver 的匹配?
? ? ??我們在上一篇還有 platform_device 中,是利用 .name 來實現(xiàn)device與driver的匹配的,但現(xiàn)在設(shè)備樹替換掉了device,那我們將如何實現(xiàn)二者的匹配呢?有了設(shè)備樹后,platform比較的名字存在哪?
? ? ?我們先看一下原來是如何匹配的 ,platform_bus_type 下有個match成員,platform_match 定義如下
[cpp]?view plaincopy
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);?? ?? ?????? ????if?(of_driver_match_device(dev,?drv))?? ????????return?1;?? ?? ?????? ????if?(acpi_driver_match_device(dev,?drv))?? ????????return?1;?? ?? ?????? ????if?(pdrv->id_table)?? ????????return?platform_match_id(pdrv->id_table,?pdev)?!=?NULL;?? ?? ?????? ????return?(strcmp(pdev->name,?drv->name)?==?0);?? }??
其中又調(diào)用了of_driver_match_device(dev, drv) ,其定義如下:
[cpp]?view plaincopy
static?inline?int?of_driver_match_device(struct?device?*dev,?? ?????????????????????const?struct?device_driver?*drv)?? {?? ????return?of_match_device(drv->of_match_table,?dev)?!=?NULL;?? }??
其調(diào)用of_match_device(drv->of_match_table, dev) ,繼續(xù)追蹤下去,注意這里的參數(shù)
drv->of_match_table
[cpp]?view plaincopy
const?struct?of_device_id?*of_match_device(const?struct?of_device_id?*matches,?? ???????????????????????const?struct?device?*dev)?? {?? ????if?((!matches)?||?(!dev->of_node))?? ????????return?NULL;?? ????return?of_match_node(matches,?dev->of_node);?? }?? EXPORT_SYMBOL(of_match_device);??
又調(diào)用 of_match_node(matches, dev->of_node) ?,其中matches 是
struct of_device_id 類型
的
[cpp]?view plaincopy
? ? ? ? ? ? ?? const?struct?of_device_id?*of_match_node(const?struct?of_device_id?*matches,?? ?????????????????????const?struct?device_node?*node)?? {?? ????const?struct?of_device_id?*match;?? ????unsigned?long?flags;?? ?? ????raw_spin_lock_irqsave(&devtree_lock,?flags);?? ????match?=?__of_match_node(matches,?node);?? ????raw_spin_unlock_irqrestore(&devtree_lock,?flags);?? ????return?match;?? }?? EXPORT_SYMBOL(of_match_node);??
找到 match = __of_match_node(matches, node); 注意著里的node是struct device_node 類型的
[cpp]?view plaincopy
const?struct?of_device_id?*__of_match_node(const?struct?of_device_id?*matches,?? ???????????????????????const?struct?device_node?*node)?? {?? ????const?struct?of_device_id?*best_match?=?NULL;?? ????int?score,?best_score?=?0;?? ?? ????if?(!matches)?? ????????return?NULL;?? ?? ????for?(;?matches->name[0]?||?matches->type[0]?||?matches->compatible[0];?matches++)?{?? ????????score?=?__of_device_is_compatible(node,?matches->compatible,?? ??????????????????????????matches->type,?matches->name);?? ????????if?(score?>?best_score)?{?? ????????????best_match?=?matches;?? ????????????best_score?=?score;?? ????????}?? ????}?? ?? ????return?best_match;?? }??
繼續(xù)追蹤下去
[cpp]?view plaincopy
static?int?__of_device_is_compatible(const?struct?device_node?*device,?? ?????????????????????const?char?*compat,?const?char?*type,?const?char?*name)?? {?? ????struct?property?*prop;?? ????const?char?*cp;?? ????int?index?=?0,?score?=?0;?? ?? ?????? ????if?(compat?&&?compat[0])?{?? ????????prop?=?__of_find_property(device,?"compatible",?NULL);?? ????????for?(cp?=?of_prop_next_string(prop,?NULL);?cp;?? ?????????????cp?=?of_prop_next_string(prop,?cp),?index++)?{?? ????????????if?(of_compat_cmp(cp,?compat,?strlen(compat))?==?0)?{?? ????????????????score?=?INT_MAX/2?-?(index?<<?2);?? ????????????????break;?? ????????????}?? ????????}?? ????????if?(!score)?? ????????????return?0;?? ????}?? ?? ?????? ????if?(type?&&?type[0])?{?? ????????if?(!device->type?||?of_node_cmp(type,?device->type))?? ????????????return?0;?? ????????score?+=?2;?? ????}?? ?? ?????? ????if?(name?&&?name[0])?{?? ????????if?(!device->name?||?of_node_cmp(name,?device->name))?? ????????????return?0;?? ????????score++;?? ????}?? ?? ????return?score;?? }??
看這句 prop = __of_find_property(device, "compatible", NULL);
可以發(fā)先追溯到底,是利用"compatible"來匹配的,即設(shè)備樹加載之后,內(nèi)核會自動把設(shè)備樹節(jié)點轉(zhuǎn)換成 platform_device這種格式,同時把名字放到of_node這個地方。
? ?
platform_driver 部分
可以看到原來是利用platform_driver 下的 struct driver 結(jié)構(gòu)體中的 name 成員來匹配的,看一下 struct driver 結(jié)構(gòu)體的定義:
[cpp]?view plaincopy
struct?device_driver?{?? ????const?char??????*name;?? ????struct?bus_type?????*bus;?? ?? ????struct?module???????*owner;?? ????const?char??????*mod_name;???? ?? ????bool?suppress_bind_attrs;????? ?? ????const?struct?of_device_id???*of_match_table;?? ????const?struct?acpi_device_id?*acpi_match_table;?? ?? ????int?(*probe)?(struct?device?*dev);?? ????int?(*remove)?(struct?device?*dev);?? ????void?(*shutdown)?(struct?device?*dev);?? ????int?(*suspend)?(struct?device?*dev,?pm_message_t?state);?? ????int?(*resume)?(struct?device?*dev);?? ????const?struct?attribute_group?**groups;?? ?? ????const?struct?dev_pm_ops?*pm;?? ?? ????struct?driver_private?*p;?? }?? ? ? ? 成員中有const struct of_device_id
*of_match_table; 是struct of_device_id 類型,定義如下:
[cpp]?view plaincopy
? ? ?? struct?of_device_id?? {?? ????char????name[32];?? ????char????type[32];?? ????char????compatible[128];?? ????const?void?*data;?? };?? ? ? ? 可以看到其作用就是為了匹配一個設(shè)備。我們所要做的就是對 char?compatible[128] 的填充;設(shè)備樹加載之后,內(nèi)核會自動把設(shè)備樹節(jié)點轉(zhuǎn)換成 platform_device這種格式,同時把名字放到of_node這個地方。
3、基于設(shè)備樹的driver的結(jié)構(gòu)體的填充
? ? ? 匹配的方式發(fā)生了改變,那我們的platform_driver 也要修改了
基于設(shè)備樹的driver的結(jié)構(gòu)體的填充:
[cpp]?view plaincopy
static?struct?of_device_id?beep_table[]?=?{?? ????{.compatible?=?"fs4412,beep"},?? };?? static?struct?platform_driver?beep_driver=?? {?? ????.probe?=?beep_probe,?? ????.remove?=?beep_remove,?? ????.driver={?? ????????.name?=?"bigbang",?? ????????.of_match_table?=?beep_table,?? ????},?? };??
原來的driver是這樣的,可以對比一下
[cpp]?view plaincopy
static?struct?platform_driver?beep_driver=?? {?? ????.driver.name?=?"bigbang",?? ????.probe?=?beep_probe,?? ????.remove?=?beep_remove,?? };??
4、設(shè)備樹編譯
? ? ? 我們在?arch/arm/boot/dts/exynos4412-fs4412.dts 中添加
[cpp]?view plaincopy
fs4412-beep{?? ?????????compatible?=?"fs4412,beep";?? ?????????reg?=?<0x114000a0?0x4?0x139D0000?0x14>;?? };??
就可以編譯設(shè)備樹了
make?dtbs ?在內(nèi)核根目錄 vim?arch/arm/boot/dts/exynos4412-fs4412.dts sudo?cp??arch/arm/boot/dts/exynos4412-fs4412.dtb?/tftpboot/ |
? ? ?然后,將設(shè)備樹下載到0x42000000處,并加載驅(qū)動 insmod driver.ko, 測試下驅(qū)動。
總結(jié)
以上是生活随笔為你收集整理的Linux 设备驱动开发 —— 设备树在platform设备驱动中的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。