《內核對設備樹的處理》


1.內核對設備樹的處理

  從源代碼文件 dts 文件開始,設備樹的處理過程為: 

  ① dts 在 PC 機上被編譯為 dtb 文件;
  ② u-boot 把 dtb 文件傳給內核;
  ③ 內核解析 dtb 文件,把每一個節點都轉換為 device_node 結構體;
  ④ 對於某些 device_node 結構體,會被轉換為 platform_device 結構體。

 

2.dtb 中每一個節點都被轉換為 device_node 結構體

   根節點被保存在全局變量 of_root 中, 從 of_root 開始可以訪問到任意節點。

 

3.哪些設備樹節點會被轉換為 platform_device

3.1 根節點下含有 compatile 屬性的子節點 。

3.2 含有特定 compatile 屬性的節點的子節點。

  如果一個節點的 compatile 屬性,它的值是這 4 者之一: "simple-bus","simple-mfd","isa","arm,amba-bus", 那么它的子結點(需含 compatile 屬性)也可以轉換為 platform_device。

3.3 總線 I2C、 SPI 節點下的子節點: 不轉換為 platform_device 。

  某個總線下到子節點, 應該交給對應的總線驅動程序來處理, 它們不應該被轉換為platform_device。 

 

  比如以下的節點中:

  /mytest 會被轉換為 platform_device, 因為它兼容"simple-bus";它的子節點/mytest/mytest@0 也會被轉換為 platform_device。/i2c 節點一般表示 i2c 控制器, 它會被轉換為 platform_device, 在內核中有對應的 platform_driver;
  /i2c/at24c02 節點不會被轉換為 platform_device, 它被如何處理完全由父節點的platform_driver 決定, 一般是被創建為一個 i2c_client。 

  類似的也有/spi 節點, 它一般也是用來表示 SPI 控制器, 它會被轉換為 platform_device,在內核中有對應的 platform_driver; 

  /spi/flash@0 節點不會被轉換為 platform_device, 它被如何處理完全由父節點的platform_driver 決定, 一般是被創建為一個 spi_device。 

/ {
        mytest {
            compatile = "mytest", "simple-bus";
            mytest@0 {
                compatile = "mytest_0";
            };
        };

        i2c {
            compatile = "samsung,i2c";
            at24c02 {
                compatile = "at24c02";
            };    
        };

        spi {
           compatile = "samsung,spi";
           flash@0 {
                compatible = "winbond,w25q32dw";
                spi-max-frequency = <25000000>;
                reg = <0>;
            };
        };
};

 

4.怎么轉換為 platform_device
  platform_device 中含有 resource 數組, 它來自 device_node 的 reg, interrupts 屬性。
  platform_device.dev.of_node 指向 device_node, 可以通過它獲得其他屬性。  

 

5.platform_device 如何與 platform_driver 配對
  從設備樹轉換得來的 platform_device 會被注冊進內核里,以后當我們每注冊一個platform_driver 時, 它們就會兩兩確定能否配對,如果能配對成功就調用 platform_driver 的
probe 函數。 

  

   其中1.3.4在之前的隨筆中已經討論過了。這邊直接討論2中用設備樹匹配。

  比較: platform_device. dev.of_node 和 platform_driver.driver.of_match_table。

  由設備樹節點轉換得來的 platform_device 中,含有一個結構體: of_node。

它的類型如下: 
  

 

  如果一個 platform_driver 支持設備樹,它的 platform_driver.driver.of_match_table 是一個數組,類型如下: 
  

   使用設備樹信息來判斷 dev 和 drv 是否配對時,

  首先,如果 of_match_table 中含有 compatible 值,就跟 dev 的 compatile 屬性比較,若一致則成功,否則返回失敗; 
  其次,如果 of_match_table 中含有 type 值,就跟 dev 的 device_type 屬性比較,若一致則成功,否則返回失敗; 

  最后,如果 of_match_table 中含有 name 值,就跟 dev 的 name 屬性比較,若一致則成功,否則返回失敗。 

  而設備樹中建議不再使用 devcie_type 和 name 屬性,所以基本上只使用設備節點的compatible 屬性來尋找匹配的 platform_driver。 

 

6.沒有轉換為 platform_device 的節點,如何使用

  任意驅動程序里,都可以直接訪問設備樹,可以使用以下介紹的函數找到節點,讀出里面的值。

6.1內核中設備樹相關的頭文件介紹

  內核源碼中 include/linux/目錄下有很多 of 開頭的頭文件, of 表示“open firmware”即開放固件。
  設備樹的處理過程是: dtb -> device_node -> platform_device。

處理 DTB :

of_fdt.h     // dtb 文件的相關操作函數, 我們一般用不到,
               // 因為 dtb 文件在內核中已經被轉換為 device_node 樹(它更易於使用)

 

處理 device_node :

of.h               // 提供設備樹的一般處理函數,
                    // 比如 of_property_read_u32(讀取某個屬性的 u32 值),
               // of_get_child_count(獲取某個 device_node 的子節點數)
of_address.h     // 地址相關的函數,
            // 比如 of_get_address(獲得 reg 屬性中的 addr, size 值)
            // of_match_device (從 matches 數組中取出與當前設備最匹配的一項)
of_dma.h       // 設備樹中 DMA 相關屬性的函數
of_gpio.h       // GPIO 相關的函數
of_graph.h      // GPU 相關驅動中用到的函數, 從設備樹中獲得 GPU 信息
of_iommu.h      // 很少用到
of_irq.h       // 中斷相關的函數
of_mdio.h       // MDIO (Ethernet PHY) API
of_net.h       // OF helpers for network devices.
of_pci.h       // PCI 相關函數
of_pdt.h       // 很少用到
of_reserved_mem.h // reserved_mem 的相關函數

 

處理 platform_device:

of_platform.h     // 把 device_node 轉換為 platform_device 時用到的函數,
            // 比如 of_device_alloc(根據 device_node 分配設置 platform_device),
            // of_find_device_by_node (根據 device_node 查找到 platform_device),
            // of_platform_bus_probe (處理 device_node 及它的子節點)
of_device.h      // 設備相關的函數, 比如 of_match_device

 

6.2platform_device 相關的函數

  of_platform.h 中聲明了很多函數,但是作為驅動開發者,我們只使用其中的 1、 2 個。其他的都是給內核自己使用的,內核使用它們來處理設備樹,轉換得到 platform_device。 

函數原型為:
extern struct platform_device *of_find_device_by_node(struct device_node *np);

  設備樹中的每一個節點,在內核里都有一個 device_node;你可以使用 device_node 去找到對應的 platform_device。 
  

函數原型為:
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type // 取哪類資源? IORESOURCE_MEM、 IORESOURCE_REG
* // IORESOURCE_IRQ 等
* @num: resource index // 這類資源中的哪一個?
*/
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);

  這個函數跟設備樹沒什么關系, 但是設備樹中的節點被轉換為 platform_device 后, 設備樹中的 reg 屬性、 interrupts 屬性也會被轉換為“resource”。 
  這時,你可以使用這個函數取出這些資源。 

  對於設備樹節點中的 reg 屬性,它屬性 IORESOURCE_MEM 類型的資源;
  對於設備樹節點中的 interrupts 屬性,它屬性 IORESOURCE_IRQ 類型的資源。

6.3有些節點不會生成 platform_device,怎么訪問它們

  內核會把 dtb 文件解析出一系列的 device_node 結構體,我們可以直接訪問這些device_node。
  內核源碼 incldue/linux/of.h 中聲明了 device_node 和屬性 property 的操作函數,device_node 和 property 的結構體定義如下: 
  

 

6.3.1 找到節點

of_find_node_by_path

static inline struct device_node *of_find_node_by_path(const char *path);

  根據路徑找到節點,比如“/”就對應根節點,“/memory”對應 memory 節點。

of_find_node_by_name

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

  根據名字找到節點, 節點如果定義了 name 屬性,那我們可以根據名字找到它。

  參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
  但是在設備樹的官方規范中不建議使用“name”屬性,所以這函數也不建議使用。 

of_find_node_by_type

 

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

  根據類型找到節點,節點如果定義了 device_type 屬性,那我們可以根據類型找到它。

  參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
  但是在設備樹的官方規范中不建議使用“device_type”屬性,所以這函數也不建議使用。

of_find_compatible_node

extern struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compat);

  根據 compatible 找到節點,節點如果定義了 compatible 屬性,那我們可以根據compatible 屬性找到它。 
  參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
  參數 compat 是一個字符串,用來指定 compatible 屬性的值;
  參數 type 是一個字符串,用來指定 device_type 屬性的值,可以傳入 NULL。
of_find_node_by_phandle

extern struct device_node *of_find_node_by_phandle(phandle handle);

  根據 phandle 找到節點。
  dts 文件被編譯為 dtb 文件時,每一個節點都有一個數字 ID,這些數字 ID 彼此不同。
  可以使用數字 ID 來找到 device_node。這些數字 ID 就是 phandle。 

  參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。 

of_get_parent

extern struct device_node *of_get_parent(const struct device_node *node);

  找到 device_node 的父節點。

  參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
of_get_next_parent
  這個函數名比較奇怪,怎么可能有“next parent”?
  它實際上也是找到 device_node 的父節點,跟 of_get_parent 的返回結果是一樣的。
  差別在於它多調用下列函數,把 node 節點的引用計數減少了 1。這意味着調用of_get_next_parent 之后,你不再需要調用 of_node_put 釋放 node 節點。

of_node_put(node);
extern struct device_node *of_get_next_parent(struct device_node *node);

  參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。

of_get_next_child

extern struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);

  取出下一個子節點。

  參數 node 表示父節點;
  prev 表示上一個子節點,設為 NULL 時表示想找到第 1 個子節點。
  不斷調用 of_get_next_child 時,不斷更新 pre 參數, 就可以得到所有的子節點。

of_get_next_available_child

struct device_node *of_get_next_available_child(const struct device_node *node,struct device_node *prev);

  取出下一個“可用”的子節點,有些節點的 status 是“disabled”,那就會跳過這些節點。

  參數 node 表示父節點;
  prev 表示上一個子節點,設為 NULL 時表示想找到第 1 個子節點。

of_get_child_by_name

extern struct device_node *of_get_child_by_name(const struct device_node *node,const char *name);

  根據名字取出子節點。

  參數 node 表示父節點;
  name 表示子節點的名字。


6.3.2 找到屬性

  內核源碼 incldue/linux/of.h 中聲明了 device_node 的操作函數,當然也包括屬性的操作函數。 
of_find_property

extern struct property *of_find_property(const struct device_node *np,const char *name,int *lenp);

  找到節點中的屬性。

  參數 np 表示節點,我們要在這個節點中找到名為 name 的屬性。
  lenp 用來保存這個屬性的長度, 即它的值的長度。

  在設備樹中,節點大概是這樣

xxx_node {
    xxx_pp_name = “hello”;
};

  上述節點中,“xxx_pp_name”就是屬性的名字,值的長度是 6。

6.3.3獲取屬性的值

of_get_property

/*
* Find a property with a given name for a given node
* and return the value.
*/
const void *of_get_property(const struct device_node *np, const char *name,int *lenp)

  根據名字找到節點的屬性,並且返回它的值。

  參數 np 表示節點,我們要在這個節點中找到名為 name 的屬性,然后返回它的值。
  lenp 用來保存這個屬性的長度, 即它的值的長度。

of_property_count_elems_of_size 

* of_property_count_elems_of_size - Count the number of elements in a property
*
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
* @elem_size: size of the individual element
*
* Search for a property in a device node and count the number of elements of
* size elem_size in it. Returns number of elements on sucess, -EINVAL if the
* property does not exist or its length does not match a multiple of elem_size
* and -ENODATA if the property does not have a value.
*/
int of_property_count_elems_of_size(const struct device_node *np,const char *propname, int elem_size)

  根據名字找到節點的屬性,確定它的值有多少個元素(elem)。

  參數 np 表示節點,我們要在這個節點中找到名為 propname 的屬性,然后返回下列結果:

return prop->length / elem_size;

  在設備樹中,節點大概是這樣:

xxx_node {
    xxx_pp_name = <0x50000000 1024> <0x60000000 2048>;
};

  調用 of_property_count_elems_of_size(np, “xxx_pp_name”, 8)時,返回值是 2;
  調用 of_property_count_elems_of_size(np, “xxx_pp_name”, 4)時,返回值是 4。

6.3.4讀整數 u32/u64
of_property_read_u32
of_property_read_u64

static inline int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value);

extern int of_property_read_u64(const struct device_node *np,const char *propname, u64 *out_value);

  在設備樹中,節點大概是這樣:

xxx_node {
    name1 = <0x50000000>;
    name2 = <0x50000000 0x60000000>;
};

  調用 of_property_read_u32 (np, “name1”, &val)時, val 將得到值 0x50000000;
  調用 of_property_read_u64 (np, “name2”, &val)時, val 將得到值 0x0x6000000050000000。

6.3.5讀某個整數 u32/u64
of_property_read_u32_index

extern int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);

  在設備樹中,節點大概是這樣:

xxx_node {
    name2 = <0x50000000 0x60000000>;
};

  調用 of_property_read_u32 (np, “name2”, 1, &val)時, val 將得到值 0x0x60000000。

6.3.6讀數組

of_property_read_variable_u8_array

of_property_read_variable_u16_array

of_property_read_variable_u32_array

of_property_read_variable_u64_array

int of_property_read_variable_u8_array(const struct device_node *np,const char *propname, u8 *out_values,size_t sz_min, size_t sz_max);

int of_property_read_variable_u16_array(const struct device_node *np,const char *propname, u16 *out_values,size_t sz_min, size_t sz_max);

int of_property_read_variable_u32_array(const struct device_node *np,const char *propname, u32 *out_values,size_t sz_min, size_t sz_max);

int of_property_read_variable_u64_array(const struct device_node *np,const char *propname, u64 *out_values,size_t sz_min, size_t sz_max);

  在設備樹中,節點大概是這樣:

xxx_node {
    name2 = <0x50000012 0x60000034>;
};

  上述例子中屬性 name2 的值,長度為 8。
  調用 of_property_read_variable_u8_array (np, “name2”, out_values, 1, 10)時, out_values中將會保存這 8 個字節: 0x12,0x00,0x00,0x50,0x34,0x00,0x00,0x60。
  調用 of_property_read_variable_u16_array (np, “name2”, out_values, 1, 10)時, out_values中將會保存這 4 個 16 位數值: 0x0012, 0x5000,0x0034,0x6000。 

  總之,這些函數要么能取到全部的數值,要么一個數值都取不到;
  如果值的長度在 sz_min 和 sz_max 之間,就返回全部的數值;否則一個數值都不返回。

6.3.7讀字符串

of_property_read_string

int of_property_read_string(const struct device_node *np, const char *propname,const char **out_string);

  返回節點 np 的屬性(名為 propname)的值, (*out_string)指向這個值,把它當作字符串。 

7.怎么修改設備樹文件

  一個寫得好的驅動程序, 它會盡量確定所用資源。只把不能確定的資源留給設備樹, 讓設備樹來指定。
  根據原理圖確定"驅動程序無法確定的硬件資源", 再在設備樹文件中填寫對應內容。那么, 所填寫內容的格式是什么? 
  有些芯片,廠家提供了對應的設備樹生成工具,可以選擇某個引腳用於某些功能,就可以自動生成設備樹節點。你再把這些節點復制到內核的設備樹文件里即可。 
  看綁定文檔。內核文檔 Documentation/devicetree/bindings/ ,做得好的廠家也會提供設備樹的說明文檔。
  也可以參考同類型單板的設備樹文件 。

 

 

 



 

 

 

 

 

  

 

 

















 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM