天天看點

linux ARM裝置樹

1. 裝置樹(Device Tree)基本概念及作用

在核心源碼中,存在大量對闆級細節資訊描述的代碼。這些代碼充斥在/arch/arm/plat-xxx和/arch/arm/mach-xxx目錄,對核心而言這些platform裝置、resource、i2c_board_info、spi_board_info以及各種硬體的platform_data絕大多數純屬垃圾備援代碼。為了解決這一問題,ARM核心版本3.x之後引入了原先在Power PC等其他體系架構已經使用的Flattened Device Tree。

“A data structure by which bootloaders pass hardware layout to Linux in a device-independent manner, simplifying hardware probing.”開源文檔中對裝置樹的描述是,一種描述硬體資源的資料結構,它通過bootloader将硬體資源傳給核心,使得核心和硬體資源描述相對獨立(也就是說*.dtb檔案由Bootloader讀入記憶體,之後由核心來解析)。

Device Tree可以描述的資訊包括CPU的數量和類别、記憶體基位址和大小、總線和橋、外設連接配接、中斷控制器和中斷使用情況、GPIO控制器和GPIO使用情況、Clock控制器和Clock使用情況。

另外,裝置樹對于可熱插拔的熱備不進行具體描述,它隻描述用于控制該熱插拔裝置的控制器。

裝置樹的主要優勢:對于同一SOC的不同主機闆,隻需更換裝置樹檔案.dtb即可實作不同主機闆的無差異支援,而無需更換核心檔案。

注:要使得3.x之後的核心支援使用裝置樹,除了核心編譯時需要打開相對應的選項外,bootloader也需要支援将裝置樹的資料結構傳給核心。

2. 裝置樹的組成和使用

裝置樹包含DTC(device tree compiler),DTS(device tree source和DTB(device tree blob)。其對應關系如圖1-1所示:

linux ARM裝置樹

圖1-1 DTS、DTC、DTB之間的關系

2.1. DTS和DTSI

*.dts檔案是一種ASCII文本對Device Tree的描述,放置在核心的/arch/arm/boot/dts目錄。一般而言,一個*.dts檔案對應一個ARM的machine。

*.dtsi檔案作用:由于一個SOC可能有多個不同的電路闆,而每個電路闆擁有一個 *.dts。這些dts勢必會存在許多共同部分,為了減少代碼的備援,裝置樹将這些共同部分提煉儲存在*.dtsi檔案中,供不同的dts共同使用。*.dtsi的使用方法,類似于C語言的頭檔案,在dts檔案中需要進行include *.dtsi檔案。當然,dtsi本身也支援include 另一個dtsi檔案。

2.2. DTC

DTC為編譯工具,它可以将.dts檔案編譯成.dtb檔案。DTC的源碼位于核心的scripts/dtc目錄,核心選中CONFIG_OF,編譯核心的時候,主機可執行程式DTC就會被編譯出來。 即scripts/dtc/Makefile中

hostprogs-y := dtc

always := $(hostprogs-y)

在核心的arch/arm/boot/dts/Makefile中,若選中某種SOC,則與其對應相關的所有dtb檔案都将編譯出來。在linux下,make dtbs可單獨編譯dtb。以下截取了TEGRA平台的一部分。

ifeq ($(CONFIG_OF),y)

dtb-$(CONFIG_ARCH_TEGRA) += tegra20-harmony.dtb \

tegra30-beaver.dtb \

tegra114-dalmore.dtb \

tegra124-ardbeg.dtb

2.3. DTB

DTC編譯*.dts生成的二進制檔案(*.dtb),bootloader在引導核心時,會預先讀取*.dtb到記憶體,進而由核心解析。

2.4. Bootloader

Bootloader需要将裝置樹在記憶體中的位址傳給核心。在ARM中通過bootm或bootz指令來進行傳遞。bootm [kernel_addr] [initrd_address] [dtb_address],其中kernel_addr為核心鏡像的位址,initrd為initrd的位址,dtb_address為dtb所在的位址。若initrd_address為空,則用“-”來代替。

3. 裝置樹中dts、dtsi檔案的基本文法

DTS的基本文法範例,如圖3-1 所示。

它包括一系列節點,以及描述節點的屬性。

“/”為root節點。在一個.dts檔案中,有且僅有一個root節點;在root節點下有“node1”,“node2”子節點,稱root為“node1”和“node2”的parent節點,除了root節點外,每個節點有且僅有一個parent;其中子節點node1下還存在子節點“child-nodel1”和“child-node2”。

注:如果看過核心/arch/arm/boot/dts目錄的讀者看到這可能有一個疑問。在每個.dsti和.dts中都會存在一個“/”根節點,那麼如果在一個裝置樹檔案中include一個.dtsi檔案,那麼豈不是存在多個“/”根節點了麼。其實不然,編譯器DTC在對.dts進行編譯生成dtb時,會對node進行合并操作,最終生成的dtb隻有一個root node。Dtc會進行合并操作這一點從屬性上也可以得到驗證。這個稍後做講解。

在節點的{}裡面是描述該節點的屬性(property),即裝置的特性。它的值是多樣化的:

1.它可以是字元串string,如①;也可能是字元串數組string-list,如②

2.它也可以是32 bit unsigned integers,如cell⑧,整形用<>表示

3.它也可以是binary data,如③,十六進制用[]表示

4.它也可能是空,如⑦

linux ARM裝置樹

圖3-1 DTS的基本文法範例

在/arch/arm/boot/dts/目錄中有一個檔案skeleton.dtsi,該檔案為各ARM vendor共用的一些硬體定義資訊。以下為skeleton.dtsi的全部内容。

/ {

#address-cells = <1>;

#size-cells = <1>;

chosen { };

aliases { };

memory { device_type = "memory"; reg = <0 0>; };

};

如上,屬性# address-cells的值為1,它代表以“/”根節點為parent的子節點中,reg屬性中存在一個address值;#size-cells的值為1,它代表以“\” 根節點為parent的子節點中,reg屬性中存在一個size值。即父節點的# address-cells和#size-cells決定了子節點的address和size的長度;Reg的組織形式為reg =

下面列舉例子,對一些典型節點進行具體描述。

3.1. chosen node

chosen {

bootargs = "tegraid=40.0.0.00.00 vmalloc=256M video=tegrafb console=ttyS0,115200n8 earlyprintk";

};

chosen node 主要用來描述由系統指定的runtime parameter,它并沒有描述任何硬體裝置節點資訊。原先通過tag list傳遞的一些linux kernel運作的參數,可以通過chosen節點來傳遞。如command line可以通過bootargs這個property來傳遞。如果存在chosen node,它的parent節點必須為“/”根節點。

3.3. aliases node

aliases {

i2c6 = &pca9546_i2c0;

i2c7 = &pca9546_i2c1;

i2c8 = &pca9546_i2c2;

i2c9 = &pca9546_i2c3;

};

aliases node用來定義别名,類似C++中引用。上面是一個在.dtsi中的典型應用,當使用i2c6時,也即使用pca9546_i2c0,使得引用節點變得簡單友善。例:當.dts include 該.dtsi時,将i2c6的status屬性指派為okay,則表明該主機闆上的pca9546_i2c0處于enable狀态;反之,status指派為disabled,則表明該主機闆上的pca9546_i2c0處于disenable狀态。如下是引用的具體例子:

&i2c6 {--------------這裡&i2c6到底是label還是alias???

status = "okay";

};------------------在*.dtsi中大多預設為裝置為disable,然後在*.dts中将其enable,進行重寫使能。

3.3. memory node

memory {

device_type = "memory";

reg = <0x00000000 0x20000000>;

};

對于memory node,device_type必須為memory,由之前的描述可以知道該memory node是以0x00000000為起始位址,以0x20000000為結束位址的512MB的空間。

一般而言,在.dts中不對memory進行描述,而是通過bootargs中類似[email protected]的方式傳遞給核心。

3.4. 其他節點

由于其他裝置節點依據屬性進行描述,具有類似的形式。接下來的部分主要分析各種屬性的含義及作用,并結合相關的例子進行闡述。

3.4.1. Reg屬性

在device node 中,reg是描述memory-mapped IO register的offset和length。子節點的reg屬性address和length長度取決于父節點對應的#address-cells和#size-cells的值。例:

linux ARM裝置樹

在上述的aips節點中,存在子節點spda。spda中的中reg為<0x70000000 0x40000 >,其0x700000000為address,0x40000為size。這一點在圖3-1下有作介紹。

這裡補充的一點是:裝置節點的名稱格式[email protected],節點名稱用node-name唯一辨別,為一個ASCII字元串。其中@unit-address為可選項,可以不作描述。unit-address的具體格式和裝置挂載在哪個bus上相關。如:cpu的unit-address從0開始編址,以此加1;本例中,aips為0x70000000。

3.4.2. compatible屬性

在①中,compatible屬性為string list,用來将裝置比對對應的driver驅動,優先級為從左向右。本例中spba的驅動優先考慮“fsl,aips-bus”驅動;若沒有“fsl,aips-bus”驅動,則用字元串“simple-bus”來繼續尋找合适的驅動。即compatible實作了原先核心版本3.x之前,platform_device中.name的功能,至于具體的實作方法,本文後面會做講解。

注:對于“/”root節點,它也存在compatible屬性,用來比對machine type。具體說明将在後面給出。

3.4.3. interrupts屬性

linux ARM裝置樹

裝置節點通過interrupt-parent來指定它所依附的中斷控制器,當節點沒有指定interrupt-parent時,則從parent節點中繼承。上面例子中,root節點的interrupt-parent = <&mic>。這裡使用了引用,即mic引用了②中的inrerrupt-controller @40008000;root節點的子節點并沒有指定interrupt-controller,如ahb、fab,它們均使用從根節點繼承過來的mic,即位于0x40008000的中斷控制器。

若子節點使用到中斷(中斷号、觸發方法等等),則需用interrupt屬性來指定,該屬性的數值長度受中斷控制器中#inrerrupt-controller值③控制,即interrupt屬性<>中數值的個數為#inrerrupt-controller的值;本例中#inrerrupt-controller=<2>,因而④中interrupts的值為<0x3d 0>形式,具體每個數值的含義由驅動實作決定。

3.4.4. ranges屬性

ranges屬性為位址轉換表,這在pcie中使用較為常見,它表明了該裝置在到parent節點中所對用的位址映射關系。ranges格式長度受目前節點#address-cell、parent節點#address-cells、目前節點#size-cell所控制。順序為ranges=<前節點#address-cell, parent節點#address-cells , 目前節點#size-cell。在本例中,目前節點#address-cell=<1>,對應于⑤中的第一個0x20000000;parent節點#address-cells=<1>,對應于⑤中的第二個0x20000000;目前節點#size-cell=<1>,對應于⑤中的0x30000000。即ahb0節點所占空間從0x20000000位址開始,對應于父節點的0x20000000位址開始的0x30000000位址空間大小。

注:對于相同名稱的節點,dtc會根據定義的先後順序進行合并,其相同屬性,取後定義的那個。

4. DTB相關結構

本節講下.dts編譯生成的dtb檔案,其布局結構。

linux ARM裝置樹

DTB由三部分組成:頭(Header)、結構塊(device-tree structure)、字元串塊(string block)。下面将詳細介紹這三部分的内容。

4.1. Header

在\kernel\include\linux\of_fdt.h檔案中有相關定義

linux ARM裝置樹

4.2.device-tree structure

裝置樹結構塊是一個線性化的結構體,是裝置樹的主體,以節點的形式儲存了主機闆上的裝置資訊。

在結構塊中,以宏OF_DT_BEGIN_NODE标志一個節點的開始,以宏OF_DT_END_NODE辨別一個節點的結束,整個結構塊以宏OF_DT_END (0x00000009)結束。在\kernel\include\linux\of_fdt.h中有相關定義,我們把這些宏稱之為token。

(1)FDT_BEGIN_NODE (0x00000001)。該token描述了一個node的開始位置,緊挨着該token的就是node name(包括unit address)

(2)FDT_END_NODE (0x00000002)。該token描述了一個node的結束位置。

(3)FDT_PROP (0x00000003)。該token描述了一個property的開始位置,該token之後是兩個u32的資料,分别是length和name offset。length表示該property value data的size。name offset表示該屬性字元串在device tree strings block的偏移值。length和name offset之後就是長度為length具體的屬性值資料。

(4)FDT_NOP (0x00000004)。

(5)FDT_END (0x00000009)。該token辨別了一個DTB的結束位置。

一個節點的結構如下:

(1)節點開始标志:一般為OF_DT_BEGIN_NODE(0x00000001)。

(2)節點路徑或者節點的單元名(version<3以節點路徑表示,version>=0x10以節點單元名表示)

(3)填充字段(對齊到四位元組)

(4)節點屬性。每個屬性以宏OF_DT_PROP(0x00000003)開始,後面依次為屬性值的位元組長度(4位元組)、屬性名稱在字元串塊中的偏移量(4位元組)、屬性值和填充(對齊到四位元組)。

(5)如果存在子節點,則定義子節點。

(6)節點結束标志OF_DT_END_NODE(0x00000002)。

4.3. 字元串塊

通過節點的定義知道節點都有若幹屬性,而不同的節點的屬性又有大量相同的屬性名稱,是以将這些屬性名稱提取出一張表,當節點需要應用某個屬性名稱時,直接在屬性名字段儲存該屬性名稱在字元串塊中的偏移量。

4.4. memory reserve map

這個區域包括了若幹的reserve memory描述符。每個reserve memory描述符是由address和size組成。其中address和size都是用U64來描述。

有些系統,我們也許會保留一些memory有特殊用途(例如DTB或者initrd image),或者在有些DSP+ARM的SOC platform上,有些memory被保留用于ARM和DSP進行資訊互動。這些保留記憶體不會進入記憶體管理系統。

5. 解析DTB的函數及相關資料結構

5.1. machine_desc結構

linux ARM裝置樹

核心将機器資訊記錄為machine_desc結構體(該定義在/arch/arm/include/asm/mach/arch.h),并儲存在_arch_info_begin到_arch_info_end之間(_arch_info_begin,_arch_info_end為虛拟位址,是編譯核心時指定的,此時mmu還未進行初始化。它其實通過彙編完成位址偏移操作)

machine_desc結構體用宏MACHINE_START進行定義,一般在/arch/arm/子目錄,與闆級相關的檔案中進行成員函數及變量的指派。由linker将machine_desc聚集在.arch.info.init節區形成清單。

bootloader引導核心時,ARM寄存器r2會将.dtb的首位址傳給核心,核心根據該位址,解析.dtb中根節點的compatible屬性,将該屬性與核心中預先定義machine_desc結構體的dt_compat成員做比對,得到最比對的一個machine_desc。

在代碼中,核心通過在start_kernel->setup_arch中調用setup_machine_fdt來實作上述功能,該函數的具體實作可參見/arch/arm/kernel/devtree.c。

5.2. 裝置節點結構體

1.

linux ARM裝置樹

記錄節點資訊的結構體。.dtb經過解析之後将以device_node清單的形式存儲節點資訊。

5.3. 屬性結構體

linux ARM裝置樹

device_node結構體中的成員結構體,用于描述節點屬性資訊。

5.4. uboot下的相關結構體

首先我們看下uboot用于記錄os、initrd、fdt資訊的資料結構bootm_headers,其定義在/include/image.h中,這邊截取了其中與dtb相關的一小部分。

linux ARM裝置樹

fit_hdr_fdt指向DTB裝置樹鏡像的頭。

lmb為uboot下的一種記憶體管理機制,全稱為logical memory blocks。用于管理鏡像的記憶體。lmb所記錄的記憶體資訊最終會傳遞給kernel。這裡對lmb不做展開描述。在/include/lmb.h和/lib/lmb.c中有對lmb的接口和定義的具體描述。有興趣的讀者可以看下,所包含的代碼量不多。

6. DTB加載及解析過程

linux ARM裝置樹

先從uboot裡的do_bootm出發,根據之前描述,DTB在記憶體中的位址通過bootm指令進行傳遞。在bootm中,它會根據所傳進來的DTB位址,對DTB所在記憶體做一系列操作,為核心解析DTB提供保證。上圖為對應的函數調用關系圖。

在do_bootm中,主要調用函數為do_bootm_states,第四個參數為bootm所要處理的階段和狀态。

在do_bootm_states中,bootm_start會對lmb進行初始化操作,lmb所管理的實體記憶體塊有三種方式擷取。起始位址,優先級從上往下:

1. 環境變量“bootm_low”

2. 宏CONFIG_SYS_SDRAM_BASE(在tegra124中為0x80000000)

3. gd->bd->bi_dram[0].start

大小:

1. 環境變量“bootm_size”

2. gd->bd->bi_dram[0].size

經過初始化之後,這塊記憶體就歸lmb所管轄。接着,調用bootm_find_os進行kernel鏡像的相關操作,這裡不具體闡述。

還記得之前講過bootm的三個參數麼,第一個參數核心位址已經被bootm_find_os處理,而接下來的兩個參數會在bootm_find_other中執行操作。

首先,bootm_find_other根據第二個參數找到ramdisk的位址,得到ramdisk的鏡像;然後根據第三個參數得到DTB鏡像,同檢查kernel和ramdisk鏡像一樣,檢查DTB鏡像也會進行一系列的校驗工作,如果校驗錯誤,将無法正常啟動核心。另外,uboot在确認DTB鏡像無誤之後,會将該位址儲存在環境變量“fdtaddr”中。

接着,uboot會把DTB鏡像reload一次,使得DTB鏡像所在的實體記憶體歸lmb所管理:①boot_fdt_add_mem_rsv_regions會将原先的記憶體DTB鏡像所在的記憶體置為reserve,保證該段記憶體不會被其他非法使用,保證接下來的reload資料是正确的;②boot_relocate_fdt會在bootmap區域中申請一塊未被使用的記憶體,接着将DTB鏡像内容複制到這塊區域(即歸lmb所管理的區域)

注:若環境變量中,指定“fdt_high”參數,則會根據該值,調用lmb_alloc_base函數來配置設定DTB鏡像reload的位址空間。若配置設定失敗,則會停止bootm操作。因而,不建議設定fdt_high參數。

接下來,do_bootm會根據核心的類型調用對應的啟動函數。與linux對應的是do_bootm_linux。

① boot_prep_linux

為啟動後的kernel準備參數

② boot_jump_linux

linux ARM裝置樹

以上是boot_jump_linux的片段代碼,可以看出:若使用DTB,則原先用來存儲ATAG的寄存器R2,将會用來存儲.dtb鏡像位址。

boot_jump_linux最後将調用kernel_entry,将.dtb鏡像位址傳給核心。

下面我們來看下核心的處理部分:

在arch/arm/kernel/head.S中,有這樣一段:

linux ARM裝置樹

_vet_atags定義在/arch/arm/kernel/head-common.S中,它主要對DTB鏡像做了一個簡單的校驗。

linux ARM裝置樹

真正解析處理dbt的開始部分,是setup_arch->setup_machine_fdt。這部分的處理在第五部分的machine_mdesc中有提及。

linux ARM裝置樹

如圖,是setup_machine_fdt中的解析過程。

解析chosen節點将對boot_command_line進行初始化。

解析根節點的{size,address}将對dt_root_size_cells,dt_root_addr_cells進行初始化。為之後解析memory等其他節點提供依據。

解析memory節點,将會把節點中描述的記憶體,加入memory的bank。為之後的記憶體初始化提供條件。

解析裝置樹在函數unflatten_device_tree中完成,它将.dtb解析成device_node結構(第五部分有其定義),并構成單項連結清單,以供OF的API接口使用。

下面主要結合代碼分析:/drivers/of/fdt.c

linux ARM裝置樹
linux ARM裝置樹
linux ARM裝置樹
linux ARM裝置樹
linux ARM裝置樹
linux ARM裝置樹
linux ARM裝置樹

總的歸納為:

① kernel入口處擷取到uboot傳過來的.dtb鏡像的基位址

② 通過early_init_dt_scan()函數來擷取kernel初始化時需要的bootargs和cmd_line等系統引導參數。

③ 調用unflatten_device_tree函數來解析dtb檔案,建構一個由device_node結構連接配接而成的單向連結清單,并使用全局變量of_allnodes儲存這個連結清單的頭指針。

④ 核心調用OF的API接口,擷取of_allnodes連結清單資訊來初始化核心其他子系統、裝置等。

7. OF的API接口

OF的接口函數在/drivers/of/目錄下,有of_i2c.c、of_mdio.c、of_mtd.c、Adress.c等等

這裡将列出幾個常用的API接口。

1. 用來查找在dtb中的根節點

unsigned long __init of_get_flat_dt_root(void)

2. 根據deice_node結構的full_name參數,在全局連結清單of_allnodes中,查找合适的device_node

struct device_node *of_find_node_by_path(const char *path)

例如:

struct device_node *cpus;

cpus=of_find_node_by_path("/cpus");

3. 若from=NULL,則在全局連結清單of_allnodes中根據name查找合适的device_node

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

例如:

struct device_node *np;

np = of_find_node_by_name(NULL,"firewire");

4. 根據裝置類型查找相應的device_node

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

例如:

struct device_node *tsi_pci;

tsi_pci= of_find_node_by_type(NULL,"pci");

5. 根據compatible字元串查找device_node

struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)

6. 根據節點屬性的name查找device_node

struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)

7. 根據phandle查找device_node

struct device_node *of_find_node_by_phandle(phandle handle)

8. 根據alias的name獲得裝置id号

int of_alias_get_id(struct device_node *np, const char *stem)

9. device node計數增加/減少

struct device_node *of_node_get(struct device_node *node)

void of_node_put(struct device_node *node)

10. 根據property結構的name參數,在指定的device node中查找合适的property

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

11. 根據property結構的name參數,傳回該屬性的屬性值

const void *of_get_property(const struct device_node *np, const char *name,int *lenp)

12. 根據compat參數與device node的compatible比對,傳回比對度

int of_device_is_compatible(const struct device_node *device,const char *compat)

13. 獲得父節點的device node

struct device_node *of_get_parent(const struct device_node *node)

14. 将matches數組中of_device_id結構的name和type與device node的compatible和type比對,傳回比對度最高的of_device_id結構

const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node)

15. 根據屬性名propname,讀出屬性值中的第index個u32數值給out_value

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

16. 根據屬性名propname,讀出該屬性的數組中sz個屬性值給out_values

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)

17. 根據屬性名propname,讀出該屬性的u64屬性值

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

18. 根據屬性名propname,讀出該屬性的字元串屬性值

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

19. 根據屬性名propname,讀出該字元串屬性值數組中的第index個字元串

int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output)

20. 讀取屬性名propname中,字元串屬性值的個數

int of_property_count_strings(struct device_node *np, const char *propname)

21. 讀取該裝置的第index個irq号

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

22. 讀取該裝置的第index個irq号,并填充一個irq資源結構體

int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)

23. 擷取該裝置的irq個數

int of_irq_count(struct device_node *dev)

24. 擷取裝置寄存器位址,并填充寄存器資源結構體

int of_address_to_resource(struct device_node *dev, int index,struct resource *r)

const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags)

25. 擷取經過映射的寄存器虛拟位址

void __iomem *of_iomap(struct device_node *np, int index)

24. 根據device_node查找傳回該裝置對應的platform_device結構

struct platform_device *of_find_device_by_node(struct device_node *np)

25. 根據device node,bus id以及父節點建立該裝置的platform_device結構

struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)

static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,

void *platform_data,struct device *parent)

26. 周遊of_allnodes中的節點挂接到of_platform_bus_type總線上,由于此時of_platform_bus_type總線上還沒有驅動,是以此時不進行比對

int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent)

釋出于 2019-07-19

繼續閱讀