Devices Tree加載流程


 

  1. DT.IMG布局

hdr

zImage

Ramdisk.img

DT.img

其中DT.imgDTBTOOL打包所有編譯生成的dtb生成;布局如下:

DT header

dt_entry_0

dt_entry_1

dt_entry_2

……

其中dt_entry_x對應是某棵DeviceTree編譯輸出的***.dtb

  1. Bootloader 加載DviceTree

    函數 int boot_linux_from_mmc(void)

     

     

    Bootloader

    正常啟動時把zImageramdisk.img以及某個dt_entry_xdt.img中包含多個條目)分別從存儲器(這里以eMMC為例)中讀取到RAM中的具體位置。

    具體加載哪個dt_entry_x,有bootloader根據基板信息(platform_id/target_id/soc_version)等按照某個策略找到最匹配的。

    1. 調用boot_linux();

boot_linux((void *)hdr->kernel_addr,

(void *)hdr->tags_addr,

     (const char *)hdr->cmdline,

board_machtype(),

         (void *)hdr->ramdisk_addr,

hdr->ramdisk_size);

其中參數:

kernel_addr : zImageRAM中的地址;

tags_addr : dt_entryRAM中的地址;

cmdline : 是編譯zImage時打包進去的,;

如下:

[mkbooting —kernel$KERNEL ramdisk ./booting/ramdisk $BOARD_CFG.img

—cmdline

"console=ttyHSL0,115200,n8,

androidboot.console=ttyHSL0

androidboot.hardware=qcom

user_debug=31

msm_rtb.filter=0x37"

--base 0x0000 0000—pagesize2048—ramdisk_offset 0x0200 0000

--tags_offset 0x01E0 0000 –dt ./booting/dt_$BOARD_CFG.img –output $BOOTIMG]

machtype 目前在高通平台沒有使用

ramdisk ramdiskRAM中的地址

ramdisk_size ramdisk的大小

 

  1. 調用update_device_tree();函數把commandline/ramdisk/ramdisk_size等信息更新到devicetree中的對應節點中。

update_device_tree(

(void *)tags,

(const char *)final_cmdline,

ramdisk, ramdisk_size

);

/chosen/bootargs ßfinal_cmdline

/chosen/linux,initrd-start ßramdisk

/chosen/linux,initrd-end ßramdisk+ramdisk_size

注釋:這里的final_cmdline,有boot_linux中的cmdlinelk動態配置的commandline組合而成;

比如說pwr_reasonlcd信息等。

 

  1. 調用entry(0, machtype, (unsigned*)tags_phys);啟動內核!

    向內核傳遞的信息只有machtype(unsigned*)tags_phys;其中machtype為零、tags_phys為對應的devicetreedtb)在RAM中的地址。

  1. Kernel展開DTB
    1. 內核通過DeviceTree識別特定的machineDT_MACHINE_START

      Kernel的函數在Head.S中的ENTRY(stext),此時的寄存器r1,r2分別存儲着machtypedevicetreedtb)的地址;

 

  1. 並調用kernel如下

str r1,[r5] @Save machine type

str r2,[r6] @Save atags pointer

b start_kernel

此時r1r2的值存儲到r[5],r[6];也就是_machine_arch_type_atags_pointer中,以便在C代碼空間訪問。

 

  1. 進入main.c中的start_kernel()函數,調用setup_arch()函數

進入setup.c中的setup_arch()函數,調用setup_machine_fdt()函數

進入devtree.c中的setup_machine_fdt()函數,在mdesc(即machine_desc)的table中搜索與DT數據最匹配的machine。設備樹根節點的compatible屬性跟mdesctable數組相比較決定最匹配的machine。找到最匹配的machine后,setup_machine_fdt()返回machine_desc數組的基地址,否則返回null

/**

* setup_machine_fdt - Machine setup when an dtb was passed to the kernel

* @dt_phys: physical address of dt blob

* If a dtb was passed to the kernel in r2, then use it to choose the

* correct machine_desc and to setup the system.

*/

const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)

{

    const struct machine_desc *mdesc, *mdesc_best = NULL;

#ifdef CONIG_FARCH_MULTIPLATFORM

    DT_MACHINE_START(GENERIC_DT, "Generic DT based system")

    MACHINE_END

    mdesc_best = &__mach_desc_GENERIC_DT;

#endif

    if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))

        return NULL;

    mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

    if (!mdesc) {

        const char *prop;

        int size;

        unsigned long dt_root;

        early_print("\nError: unrecognized/unsupported "

             "device tree compatible list:\n[ ");

        dt_root = of_get_flat_dt_root();

        prop = of_get_flat_dt_prop(dt_root, "compatible", &size);

        while (size > 0) {

            early_print("'%s' ", prop);

            size -= strlen(prop) + 1;

            prop += strlen(prop) + 1;

        }

        early_print("]\n\n");

        dump_machine_table(); /* does not return */

    }

    /* We really don't want to do this, but sometimes firmware provides buggy data */

    if (mdesc->dt_fixup)

        mdesc->dt_fixup();

    early_init_dt_scan_nodes();

    /* Change machine number to match the mdesc we're using */

    __machine_arch_type = mdesc->nr;

    return mdesc;

}

 

  1. 設備加載流程

    上述3得到基地址后會初始化板級信息

msm8953為例:

#include <linux/kernel.h>

#include <asm/mach/arch.h>

#include "board-dt.h"

 

static const char *msm8953_dt_match[] __initconst = {

    "qcom,msm8953",

    "qcom,apq8053",

    NULL

};

static void __init msm8953_init(void)

{

    board_dt_populate(NULL);

}

DT_MACHINE_START(MSM8953_DT,

    "Qualcomm Technologies, Inc. MSM 8953 (Flattened Device Tree)")

    .init_machine = msm8953_init,

    .dt_compat = msm8953_dt_match,

MACHINE_END

 

  1. start_kernel()開啟新的線程kernel_init(),並根據devicetree創建設備。

    start_kernel(void)—>kernel_init(void *unused)—>kernel_init_freeable()—>do_basic_setup()—>do_initcalls();do_initcalls()完成各個等級的初始化工作,涉及devicetree初始化工作如下:

static int __init customize_machine(void)

{

    of_clk_init(NULL);

    /*

     * Traverses flattened DeviceTree - registering platform devices

     * (if any) complete with their resources

     */

    of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);

    if (machine_desc->init_machine)

        machine_desc->init_machine();

    return 0;

}

arch_initcall(customize_machine);

也就是回調具體的DT_MACHINE中的 init_machinemsm8953為例就是 msm8953_init

msm8953_init()函數:

static void __init msm8953_init(void)

{

    board_dt_populate(NULL);

}

void __init board_dt_populate(struct of_dev_auxdata *adata)

{

    of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);

 

    /* Explicitly parent the /soc devices to the root node to preserve

     * the kernel ABI (sysfs structure, etc) until userspace is updated

     */

    of_platform_populate(of_find_node_by_path("/soc"),

             of_default_bus_match_table, adata, NULL);

}

of_platform_populate 遞歸完成device的創建工作。

linux設備模型里, 假設它的所有設備是連接在bus controller上的子設備.e.g. i2c_client i2c_master的子設備;唯一沒有特定父設備類型的模型就是platform_device.

調用of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)完成根設備節點創建。調用of_platform_populate(of_find_node_by_path("/soc"),of_default_bus_match_table, adata, NULL);完成/soc下相關節點設備的創建。

 

  1. Linux下的i2c驅動
    1. 設備模型

      由總線(bus_type+設備(device+驅動(device_driver)組成,在該模型下,所有的設備通過總線連接起來,即使有些設備沒有連接到一根物理總線上,linux為其設置了一個內部的、虛擬的platform總線,用以維持

      總線、驅動、設備的關系。

      對於實現一個Linux下的設備驅動,可以分為兩大步:

      1. 設備注冊
      2. 驅動注冊

      當然還有一些細節問題:

      1. 驅動的probe函數
      2. 驅動和設備是怎么綁定的

         

    2. i2c設備驅動的幾個數據結構
      1. i2c_adapter:

        每一個i2c_adapter對應一個物理上的i2c控制器,在i2c總線驅動probe函數中動態創建。通過i2c_adapter注冊到i2c_core

/*

* i2c_adapter is the structure used to identify a physical i2c bus along

* with the access algorithms necessary to access it.

*/

struct i2c_adapter {

    struct module *owner;

    unsigned int class;         /* classes to allow probing for */

    const struct i2c_algorithm *algo; /* the algorithm to access the bus */

    void *algo_data;

 

    /* data fields that are valid for all devices    */

    struct rt_mutex bus_lock;

 

    int timeout;            /* in jiffies */

    int retries;

    struct device dev;        /* the adapter device */

 

    int nr;

    char name[48];

    struct completion dev_released;

 

    struct mutex userspace_clients_lock;

    struct list_head userspace_clients;

 

    struct i2c_bus_recovery_info *bus_recovery_info;

};

 

  1. i2c_algorithm:

    i2c_algorithm中的關鍵函數master_xfer(),i2c_msg為單位產生i2c訪問需要的信號,不同平台所對應的master_xfer()是不同的,需要根據所用平台的硬件特性實現自己的xxx_xfer()方法以填充i2c_algorithmmaster_xfer指針;

/**

* struct i2c_algorithm - represent I2C transfer method

* @master_xfer: Issue a set of i2c transactions to the given I2C adapter

* defined by the msgs array, with num messages available to transfer via

* the adapter specified by adap.

* @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this

* is not present, then the bus layer will try and convert the SMBus calls

* into I2C transfers instead.

* @functionality: Return the flags that this algorithm/adapter pair supports

* from the I2C_FUNC_* flags.

*

* The following structs are for those who like to implement new bus drivers:

* i2c_algorithm is the interface to a class of hardware solutions which can

* be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584

* to name two of the most common.

*

* The return codes from the @master_xfer field should indicate the type of

* error code that occured during the transfer, as documented in the kernel

* Documentation file Documentation/i2c/fault-codes.

*/

struct i2c_algorithm {

    /* If an adapter algorithm can't do I2C-level access, set master_xfer

     to NULL. If an adapter algorithm can do SMBus access, set

     smbus_xfer. If set to NULL, the SMBus protocol is simulated

     using common I2C messages */

    /* master_xfer should return the number of messages successfully

     processed, or a negative value on error */

    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,

             int num);

    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,

             unsigned short flags, char read_write,

             u8 command, int size, union i2c_smbus_data *data)

    /* To determine what the adapter supports */

    u32 (*functionality) (struct i2c_adapter *);

};

 

  1. i2c_client:

    代表一個掛載到i2c總線上的i2c從設備,包含該設備所需要的數據:

    i2c從設備所依附的i2c控制器strut i2c_adapter *adapter

    i2c從設備的驅動程序struct i2c_driver *driver

    i2c從設備的訪問地址addr

    i2c從設備的名稱name

/**

* struct i2c_client - represent an I2C slave device

* @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;

*    I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking

* @addr: Address used on the I2C bus connected to the parent adapter.

* @name: Indicates the type of the device, usually a chip name that's

*    generic enough to hide second-sourcing and compatible revisions.

* @adapter: manages the bus segment hosting this I2C device

* @dev: Driver model device node for the slave.

* @irq: indicates the IRQ generated by this device (if any)

* @detected: member of an i2c_driver.clients list or i2c-core's

*    userspace_devices list

*

* An i2c_client identifies a single device (i.e. chip) connected to an

* i2c bus. The behaviour exposed to Linux is defined by the driver

* managing the device.

*/

struct i2c_client {

    unsigned short flags;        /* div., see below        */

    unsigned short addr;        /* chip address - NOTE: 7bit    */

                    /* addresses are stored in the    */

                    /* _LOWER_ 7 bits        */

    char name[I2C_NAME_SIZE];

    struct i2c_adapter *adapter;    /* the adapter we sit on    */

    struct device dev;        /* the device structure        */

    int irq;            /* irq issued by device        */

    struct list_head detected;

};

 

  1. i2c總線驅動
    1. 功能划分

      從硬件功能上可划分為:i2c控制器和i2c外設(從設備)。每個i2c控制器總線上都可以掛載多個i2c外設。Linux中對i2c控制器和外設分開管理:通過i2c-msm-qup.c文件完成i2c控制器的設備注冊和驅動注冊;通過i2c-core.c為具體的i2c外設提供了統一的設備注冊接口和驅動注冊接口,它分離了設備驅動和硬件控制的實現細節。

      需要注意的是:設備與驅動的對應關系是多對一的;即如果設備類型是一樣的,會共用同一套驅動。

       

    2. 設備注冊

      i2c控制器設備注冊為platform設備,為每一個控制器定義一個struct platform_device數據結構,並且把.name都設置為"i2c_qup"。后面會通過名字進行匹配驅動的。然后是調用platform_device_register()函數,將設備注冊到platform bus上。

static struct of_device_id i2c_qup_dt_match[] = {

    {

        .compatible = "qcom,i2c-qup",

    },

    {}

};

 

static struct platform_driver qup_i2c_driver = {

    .probe        = qup_i2c_probe,

    .remove        = qup_i2c_remove,

    .driver        = {

        .name    = "i2c_qup",

        .owner    = THIS_MODULE,

        .pm = &i2c_qup_dev_pm_ops,

        .of_match_table = i2c_qup_dt_match,

    },

};

設備注冊完成后其直觀的表現就是在文件系統下出現:sys/bus/platform/devices/xxx.o

通過platform_device_register()函數進行注冊的過程,就是對platform_device這個數據結構的更改,逐步完成.dev.parent/.dev.kobj/.dev.bus的賦值,然后將.dev.kobj加入到platform_busàkobj的鏈表上。

  1. 驅動注冊步驟和設備注冊類似,也是為驅動定義了一個數據結構:

static struct of_device_id i2c_qup_dt_match[] = {

    {

        .compatible = "qcom,i2c-qup",

    },

    {}

};

 

static struct platform_driver qup_i2c_driver = {

    .probe        = qup_i2c_probe,

    .remove        = qup_i2c_remove,

    .driver        = {

        .name    = "i2c_qup",

        .owner    = THIS_MODULE,

        .pm = &i2c_qup_dev_pm_ops,

        .of_match_table = i2c_qup_dt_match,

    },

};

 

/* QUP may be needed to bring up other drivers */

int __init qup_i2c_init_driver(void)

{

    static bool initialized;

 

    if (initialized)

        return 0;

    else

        initialized = true;

 

    return platform_driver_register(&qup_i2c_driver);

}

EXPORT_SYMBOL(qup_i2c_init_driver);

arch_initcall(qup_i2c_init_driver);

 

static void __exit qup_i2c_exit_driver(void)

{

    platform_driver_unregister(&qup_i2c_driver);

}

module_exit(qup_i2c_exit_driver);

 

  1. 設備與驅動匹配

    match過程:

    i2c_core.c:i2c_add_driver()—>i2c_register_driver()—>i2c_bus_type—>i2c_device_match()—>of_driver_match_device(),用驅動的信息與devicenode處匹配,如果相同,則匹配,匹配上之后運行driver_register調用

    driver_probe_devicedd.c中)進行設備與驅動的綁定。

  2. probe綁定過程

    初始化.probe.remove函數,然后調用i2c_add_driver進行注冊,主要調用函數流程:

    i2c_add_driver—>i2c_register_driver—>bus_add_driver—>driver_attach—>driver_probe_device—>really_probe(里面講設備的驅動指針指向驅動,如果匹配成功,執行dev>bus—>probe即設備驅動里的probe函數)—>driver_bound(綁定)

    需要注意的是driver_attach,這個函數遍歷了總線上(platform_bus_type)的所有設備,尋找與驅動匹配的設備,並把滿足條件的設備結構體上的驅動指針指向驅動,從而完成了驅動和設備的匹配(_driver_attach函數完成)

    如果匹配到設備,這是就需要執行platform_bus_typeprobe函數,最終會調用驅動的probe函數。

 

  1.  

     

     

     


免責聲明!

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



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