dts编译
2016-11-09 10:21:36 zll5258 阅读数 994

Dtc -I dts -O dts -o test.dtsmsm8939-Cp5961-p0.dts

 

Dtc  -I dts  -O dts -oteset0.dts test0000.dtb

 

反编译到teset0.dts

 

 

随便放,放到你环境变量里就可

嫌麻烦就放到和dtb dts相同的目录(不推荐)

 

Dtb 编译

一般形式:dtbTool-o path/dt.img -s 4096 -p path/scripts/dtc/ path/dts/

例子:dtbTool-o out/target/product/$TARGET_PRODUCT/dt.img -s 4096 -p out/target/product/$TARGET_PRODUCT/obj/KERNEL_OBJ/scripts/dtc/ out/target/product/$TARGET_PRODUCT/obj/KERNEL_OBJ/arch/arm64/boot/dts/

可以看下帮助

 

反编译

dtc I dtb O dts  aa.dtb –o bb.dts


 

 

Dtc工具目录:

L_5.0_0610/out/target/product/CP5961/obj/KERNEL_OBJ/scripts/dtc/dtc

 

 

 ~/dts-use/dtc -I dtb -O dtsmsm8939-CPA8-P0.dtb -o msm8939-CPA8-P0.dts

2018-09-04 10:23:30 small_a_black 阅读数 1906

dtb 反向编译生成dts

在linux 开发driver 的时候,需要check 生成的dtb 文件是否符合预期,这个时候如果可以将build 生成的dtb 文件做反编译生成对应的dts文件,对于我们check 问题,将是很有帮助的:

./dtc -I dtb -O dts *.dtb -o *.dts

这就是反编译用到的命令。至于dtc 脚本的位置,可以到如下位置去找:

kernel-verion/scripts/dtc/

再多说一句,反编译生成的dts是所有dtsi、dts文件的merge后的总文件。

2016-12-04 22:46:14 viewsky11 阅读数 2925

MSM8974 上DeviceTree

设备描述源文件放在”kernel/arch/arm/boot/dts/”下后缀是”.dts”或”.dtsi”,一般”.dtsi”是被其它文件包含的,只用dtc编译”.dts”文件。Build时会用”dtc”命令把需要的设备描述文件编译成”.dtb”文件,并放到到bootimage的某个地方。对设备描述的解析处理实现主要在”kernel/drivers/of/”目录中,需要配置”CONFIG_OF“。
启动过程中,bootloader(默认是bootable/bootloader/lk)会根据机器硬件信息选择合适的devicetree装入内存,把地址等相关信息传给kernel。kernel中,会根据传入的信息创建设备。

版本声明和包含其它文件
一般”.dts”文件会先进行版本申明,如下面的第一行。”.dts”或”.dtsi”文件也可能包含其它”.dtsi”文件,如下面的3/4行。

/dts-v1/;

/include/ "msm8974-v2.2.dtsi"
/include/ "msm8974-mtp.dtsi" 

关于取地址符号的使用
如经常碰到类似下面的写法。没找到相关文档说明(看源码来理解也很费劲)。作用应该是对之前定义过的设备(例子中是”soc”)进行补充描述。

&soc {
android_usb@fe8050c8 {
    compatible = "qcom,android-usb";
    reg = <0xfe8050c8 0xc8>;
    qcom,android-usb-swfi-latency = <1>;
};

......
};

哪些文件被编入二进制映像
有两种方式使用DT。第一种可包含多个dtb,编入dt.img,放入boot.img。第二种只包含一个dtb,直接追加到kernelimage后面,放入boot.img。
dtc编译在kernel/AndroidKernel.mk中定义。先用定义”DTS_NAMES“变量,它的每个entry(记为”DTS_NAME“变量,下面的$$arch)中可能有arch和rev两部分,和.config中相关配置有关,用下面方法找出。

while (<>) {
$$a = $$1 if /CONFIG_ARCH_((?:MSM|QSD|MPQ)[a-zA-Z0-9]+)=y/;
$$r = $$1 if /CONFIG_MSM_SOC_REV_(?!NONE)(\w+)=y/;
$$arch = $$arch.lc("$$a$$r ") if /CONFIG_ARCH_((?:MSM|QSD|MPQ)[a-zA-Z0-9]+)=y/
} print $$arch;

得到上述”DTS_NAMES“变量,用”$(DTS_NAME)*.dts“方式去”kernel/arch/arm/boot/dts/”下匹配。见下面的定义,其中”cat”命令就是生成带DT的kernelimage。

define append-dtb
mkdir -p $(KERNEL_OUT)/arch/arm/boot;\
$(foreach DTS_NAME, $(DTS_NAMES), \
   $(foreach d, $(DTS_FILES), \
  $(DTC) -p 1024 -O dtb -o $(call DTB_FILE,$(d)) $(d); \
  cat $(KERNEL_ZIMG) $(call DTB_FILE,$(d)) > $(call ZIMG_FILE,$(d));))
endef

如,msm8974的MR2的ES1版中,”DTS_NAMES“变量的值为”msm8974 msmsamarium”,会编入的文件有。

msm8974pro-ab-cdp.dts      
msm8974pro-ac-mtp.dts   
msm8974-v1-mtp.dts       
msm8974-v2.0-1-fluid.dts    
msm8974-v2.2-fluid.dts    
msmsamarium-sim.dts
msm8974pro-ab-fluid.dts    
msm8974-v1-cdp.dts      
msm8974-v1-rumi.dts      
msm8974-v2.0-1-liquid.dts   
msm8974-v2.2-liquid.dts
msm8974pro-ab-liquid.dts   
msm8974-v1-fluid.dts    
msm8974-v1-sim.dts       
msm8974-v2.0-1-mtp.dts      
msm8974-v2.2-mtp.dts
msm8974pro-ab-mtp.dts      
msm8974-v1-liquid.dts   
msm8974-v2.0-1-cdp.dts   
msm8974-v2.2-cdp.dts        
msmsamarium-rumi.dts 

第二种方式没看到后续如何放入 boot.img。对于第一种方式,会用”device/qcom/common/generate_extra_images.mk“中定义的下面规则编出”dt.img“,

$(INSTALLED_DTIMAGE_TARGET): $(DTBTOOL) $(INSTALLED_KERNEL_TARGET)
$(build-dtimage-target)

在”build/core/Makefile“中用下面语句使它被编入boot.img。

ifeq ($(strip $(BOARD_KERNEL_SEPARATED_DT)),true)
  INTERNAL_BOOTIMAGE_ARGS += --dt $(INSTALLED_DTIMAGE_TARGET)
  BOOTIMAGE_EXTRA_DEPS    := $(INSTALLED_DTIMAGE_TARGET)
endif  

dts加载流程

启动过程中,bootloader(默认是bootable/bootloader/lk)会根据机器硬件信息选择合适的devicetree装入内存,把地址等相关信息传给kernel。kernel中,会根据传入的信息创建设备。

这里写图片描述

msm8974目前实际用的应该是方式1。在下面boot_linux_from_mmc()中,调用dev_tree_get_entry_info(),里面会根据硬件(chipset和platform的id,系统实际跑时的信息在系统boot的更早阶段由N侧设置并传来,而DT中的信息由根节点的”qcom,msm-id“属性定义)来选择合适的DT,后面会把该DT装入内存,把地址等信息传给kernel(据说是通过CPU寄存器)。

    qcom,msm-id = <126 8 0x20002>,
              <185 8 0x20002>,
              <186 8 0x20002>; 

从lk中的处理
Lk/arch/arm/crt0.S文件中语句:bl kmain
调用的是lk/kernel/main.c文件中的函数:kmain()

kmain()
  |bootstrap2()
 |arch_init()
 |platform_init()
 |target_init()
 |apps_init()//call init() of APPs defined using APP_START macro
    |aboot_init()
       |boot_linux_from_mmc()
          |//for device tree approach 1
             |dev_tree_get_entry_info()
                |__dev_tree_get_entry_info()
             |memmove((void *)hdr->tags_addr, (char *)dt_table_offset + dt_entry.offset, dt_entry.size);
          |//for device tree approach 2
             |dev_tree_appended()
          |boot_linux()
             |update_device_tree()
             |entry(0, machtype, (unsigned*)tags_phys);//pass control to kernel  

详细过程
Aboot.c (bootable\bootloader\lk\app\aboot)

APP_START(aboot)
.init = aboot_init,
APP_END

在下面aboot_init()—> boot_linux_from_mmc()中,调用dev_tree_get_entry_info(),里面会根据硬件(chipset和platform的id,系统实际跑时的信息在系统boot的更早阶段由N侧设置并传来,而DT中的信息由根节点的”qcom,msm-id“属性定义)来选择合适的DT,后面会把该DT装入内存,把地址等信息传给kernel(通过CPU寄存器)。

void boot_linux(void *kernel, unsigned *tags,
                const char *cmdline, unsigned machtype,
void *ramdisk, unsigned ramdisk_size)
{
#if DEVICE_TREE
//更新Device Tree
    ret = update_device_tree((void *)tags, final_cmdline, ramdisk, ramdisk_size);
}

/* Top level function that updates the device tree. */
int update_device_tree(void *fdt, const char *cmdline,
  void *ramdisk, uint32_t ramdisk_size)
{
    int ret = 0;
    uint32_t offset;

    /* Check the device tree header */
//核查其magic数是否正确:version和size
    ret = fdt_check_header(fdt);

    /* Add padding to make space for new nodes and properties. */
//Move or resize dtb buffer
    ret = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + DTB_PAD_SIZE);

    /* Get offset of the memory node */
    ret = fdt_path_offset(fdt, "/memory");
    offset = ret;

    ret = target_dev_tree_mem(fdt, offset);
    /* Get offset of the chosen node */
    ret = fdt_path_offset(fdt, "/chosen");
    offset = ret;
    /* Adding the cmdline to the chosen node */
    ret = fdt_setprop_string(fdt, offset, (const char*)"bootargs", (const void*)cmdline);

    /* Adding the initrd-start to the chosen node */
    ret = fdt_setprop_u32(fdt, offset, "linux,initrd-start", (uint32_t)ramdisk);
    if (ret)
    /* Adding the initrd-end to the chosen node */
    ret = fdt_setprop_u32(fdt, offset, "linux,initrd-end", ((uint32_t)ramdisk + ramdisk_size));
    fdt_pack(fdt);
    return ret;
}  

Kernel中的处理
在下面的setup_machine_fdt()中,会通过用各机器描述(machine description)的”dt_compat“属性和传入DT的根节点的”compatible”属性进行匹配来选择适当的machine description。会在下面board_dt_populate()处理中,会根据devicetree信息创建设备。

主要的数据流包括:
(1)初始化流程,即扫描dtb并将其转换成Device Tree Structure。
(2)传递运行时参数传递以及platform的识别
(3)将Device Tree Structure并入linux kernel的设备驱动模型。

汇编部分的代码分析
linux/arch/arm/kernel/head.S文件定义了bootloader和kernel的参数传递要求:

MMU = off, D-cache = off, I-cache = dont care, r0 = 0, r1 = machine nr, r2 = atags or dtb pointer. 

目前的kernel支持旧的tag list的方式,同时也支持device tree的方式。r2可能是device tree binary file的指针(bootloader要传递给内核之前要copy到memory中),也可以是tag list的指针。在ARM的汇编部分的启动代码中(主要是head.S和head-common.S),machine type ID和指向DTB或者atags的指针被保存在变量__machine_arch_type__atags_pointer中,这么做是为了后续C代码进行处理。

start_kernel()
  |setup_arch()
   |setup_machine_fdt()//select machine description according to DT info  

customize_machine()//called because it is an arch_initcall
  |msm8974_init()
     |board_dt_populate()
        |of_platform_bus_create()
           |of_platform_device_create_pdata()
              |of_device_alloc()
                 |dev->dev.of_node = of_node_get(np);//pointer to data of struct device_node, that is device node in DT
           |of_platform_bus_create()//call it recursively to walk through the DT

获得machine描述符

//根据Device Tree的信息,找到最适合的machine描述符。
struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
    /* 扫描 /chosen node,保存运行时参数(bootargs)到boot_command_line,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中 */
    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
    /* 扫描根节点,获取 {size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中 */
    of_scan_flat_dt(early_init_dt_scan_root, NULL);
    /* 扫描DTB中的memory node,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息。*/
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);

    /* Change machine number to match the mdesc we're using */
    __machine_arch_type = mdesc_best->nr;
    return mdesc_best;
} 

运行时参数是在扫描DTB的chosen node时候完成的,具体的动作就是获取chosen node的bootargs、initrd等属性的value,并将其保存在全局变量(boot_command_lineinitrd_startinitrd_end)中。

将DTB转换成device node的结构的节点
在系统初始化的过程中,我们需要将DTB转换成节点是device_node的树状结构,以便后续方便操作。具体的代码位于setup_arch->unflatten_device_tree中。

void __init unflatten_device_tree(void)
{
    __unflatten_device_tree(initial_boot_params, &allnodes,
    early_init_dt_alloc_memory_arch);

    /* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */
    of_alias_scan(early_init_dt_alloc_memory_arch);
} 

unflatten_device_tree函数的主要功能就是扫描DTB,将device node被组织成:
(1)global list。全局变量struct device_node *of_allnodes就是指向设备树的global list
(2)tree。

static void __unflatten_device_tree(struct boot_param_header *blob,
    struct device_node **mynodes,
    void * (*dt_alloc)(u64 size, u64 align))
{
  //此处删除了health check代码,例如检查DTB header的magic,确认blob的确指向一个DTB。
      /* scan过程分成两轮,第一轮主要是确定device-tree structure的长度,保存在size变量中 */
    start = ((unsigned long)blob) +
    be32_to_cpu(blob->off_dt_struct);
    size = unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
    size = (size | 3) + 1;

    /* 初始化的时候,并不是扫描到一个node或者property就分配相应的内存,实际上内核是一次性的分配了一大片内存,这些内存包括了所有的struct device_node、node name、struct property所需要的内存。*/
    mem = (unsigned long)
    dt_alloc(size + 4, __alignof__(struct device_node));
    ((__be32 *)mem)[size / 4] = cpu_to_be32(0xdeadbeef);

    /* 这是第二轮的scan,第一次scan是为了得到保存所有node和property所需要的内存size,第二次就是实打实的要构建device node tree了 */
    start = ((unsigned long)blob) +
    be32_to_cpu(blob->off_dt_struct);
    unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
    //此处略去校验溢出和校验OF_DT_END。
} 

并入linux kernel的设备驱动模型
在linux kernel引入统一设备模型之后,bus、driver和device形成了设备模型中的铁三角。在驱动初始化的时候会将代表该driver的一个数据结构(一般是xxx_driver)挂入bus上的driver链表。device挂入链表分成两种情况,一种是即插即用类型的bus,在插入一个设备后,总线可以检测到这个行为并动态分配一个device数据结构(一般是xxx_device,例如usb_device),之后,将该数据结构挂入bus上的device链表。bus上挂满了driver和device,那么如何让device遇到“对”的那个driver呢?就是bus的match函数。
系统应该会根据Device tree来动态的增加系统中的platform_device(这个过程并非只发生在platform bus上,也可能发生在其他的非即插即用的bus上,例如AMBA总线、PCI总线)。 如果要并入linux kernel的设备驱动模型,那么就需要根据device_node的树状结构(root是of_allnodes)将一个个的device node挂入到相应的总线device链表中。只要做到这一点,总线机制就会安排device和driver的约会。当然,也不是所有的device node都会挂入bus上的设备链表,比如cpus node,memory node,choose node等。

没有挂入bus的device node
(1) cpus node的处理
暂无,只有choose node的相关处理。
(2) memory的处理

int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
    int depth, void *data)
{
    char *type = of_get_flat_dt_prop(node, "device_type", NULL);
    /*在初始化的时候,我们会对每一个device node都要调用该call back函数,因此,我们要过滤掉那些和memory block定义无关的node。和memory block定义有的节点有两种,一种是node name是memory@形态的,另外一种是node中定义了device_type属性并且其值是memory。*/
    if (type == NULL) {
        if (depth != 1 || strcmp(uname, "memory@0") != 0)
            return 0;
    } else if (strcmp(type, "memory") != 0)
        return 0;
    /*获取memory的起始地址和length的信息。有两种属性和该信息有关,一个是linux,usable-memory,不过最新的方式还是使用reg属性。*/
    reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
    if (reg == NULL)
        reg = of_get_flat_dt_prop(node, "reg", &l);
    if (reg == NULL)
        return 0;
    endp = reg + (l / sizeof(__be32));
    /*reg属性的值是address,size数组,那么如何来取出一个个的address/size呢?由于memory node一定是root node的child,因此dt_root_addr_cells(root node的#address-cells属性值)和dt_root_size_cells(root node的#size-cells属性值)之和就是address,size数组的entry size。*/
    while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
            u64 base, size;
        base = dt_mem_next_cell(dt_root_addr_cells, &reg);
        size = dt_mem_next_cell(dt_root_size_cells, &reg);
        if (size == 0)
            continue;
        //将具体的memory block信息加入到内核中。
        early_init_dt_add_memory_arch(base, size);
    }
    return 0;
} 

(3) interrupt controller的处理
初始化是通过start_kernel->init_IRQ->machine_desc->init_irq()实现的。我们用Qualcomm MSM 8974为例来描述interrupt controller的处理过程。下面是machine描述符的定义:/arch/arm/mach-msm/board-8974.c

DT_MACHINE_START(MSM8974_DT, "Qualcomm MSM 8974 (Flattened Device Tree)")
        .init_irq = msm_dt_init_irq,
        .dt_compat = msm8974_dt_match,
        ...
    MACHINE_END

源码文件:/arch/arm/mach-msm/board-dt.c

void __init msm_dt_init_irq(void)
{
    struct device_node *node;

    of_irq_init(irq_match);
    node = of_find_matching_node(NULL, mpm_match);
} 

of_irq_init函数:遍历Device Tree,找到匹配的irqchip。具体的代码如下:

void __init of_irq_init(const struct of_device_id *matches)
{
    /*遍历所有的node,寻找定义了interrupt-controller属性的node,如果定义了interrupt-controller属性则说明该node就是一个中断控制器。*/
    for_each_matching_node(np, matches) {
        if (!of_find_property(np, "interrupt-controller", NULL))
            continue;
    /*分配内存并挂入链表,当然还有根据interrupt-parent建立controller之间的父子关系。对于interrupt controller,它也可能是一个树状的结构。*/
        desc = kzalloc(sizeof(*desc), GFP_KERNEL);

        desc->dev = np;
        desc->interrupt_parent = of_irq_find_parent(np);
        if (desc->interrupt_parent == np)
            desc->interrupt_parent = NULL;
        list_add_tail(&desc->list, &intc_desc_list);
        }

    /*正因为interrupt controller被组织成树状的结构,因此初始化的顺序就需要控制,应该从根节点开始,依次递进到下一个level的interrupt controller。 */
    while (!list_empty(&intc_desc_list)) {
/*intc_desc_list链表中的节点会被一个个的处理,每处理完一个节点就会将该节点删除,当所有的节点被删除,整个处理过程也就是结束了。*/
        list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
            const struct of_device_id *match;
            int ret;
            of_irq_init_cb_t irq_init_cb;
    /*最开始的时候parent变量是NULL,确保第一个被处理的是root interrupt controller。在处理完root node之后,parent变量被设定为root interrupt controller,因此,第二个循环中处理的是所有parent是root interrupt controller的child interrupt controller。也就是level 1(如果root是level 0的话)的节点。*/
            if (desc->interrupt_parent != parent)
                continue;

            list_del(&desc->list);//从链表中删除
            match = of_match_node(matches, desc->dev);//匹配并初始化
            //match->data是初始化函数
            if (WARN(!match->data,
                   "of_irq_init: no init function for %s\n",
               match->compatible)) {
            kfree(desc);
            continue;
        }
        irq_init_cb = match->data;//执行初始化函数
        ret = irq_init_cb(desc->dev, desc->interrupt_parent);
        /*处理完的节点放入intc_parent_list链表,后面会用到*/
        list_add_tail(&desc->list, &intc_parent_list);
    }

    /* 对于level 0,只有一个root interrupt controller,对于level 1,可能有若干个interrupt controller,因此要遍历这些parent interrupt controller,以便处理下一个level的child node。 */
    desc = list_first_entry(&intc_parent_list, typeof(*desc), list);
    list_del(&desc->list);
    parent = desc->dev;
    kfree(desc);
    }
} 

只有该node中有interrupt-controller这个属性定义,那么linux kernel就会分配一个interrupt controller的描述符(struct intc_desc)并挂入队列。通过interrupt-parent属性,可以确定各个interrupt controller的层次关系。在scan了所有的Device Tree中的interrupt controller的定义之后,系统开始匹配过程。一旦匹配到了interrupt chip列表中的项次后,就会调用相应的初始化函数。

以i2c总线为例

以i2c总线为例加载DTS设备节点的流程

/ {  
    model = "Qualcomm MSM 8974";  
    compatible = "qcom,msm8974";  
    interrupt-parent = <&intc>;  

    aliases {  
        spi0 = &spi_0;  
        spi7 = &spi_7;  
        sdhc1 = &sdhc_1; /* SDC1 eMMC slot */  
        sdhc2 = &sdhc_2; /* SDC2 SD card slot */  
        sdhc3 = &sdhc_3; /* SDC3 SDIO slot */  
        sdhc4 = &sdhc_4; /* SDC4 SDIO slot */  
    };  

    memory {  

        secure_mem: secure_region {  
            linux,contiguous-region;  
            reg = <0 0x7800000="">;  
            label = "secure_mem";  
        };  

        adsp_mem: adsp_region {  
            linux,contiguous-region;  
            reg = <0 0x2000000="">;  
            label = "adsp_mem";  
        };  
    };  

    intc: interrupt-controller@F9000000 {  
        compatible = "qcom,msm-qgic2";  
        interrupt-controller;  
        #interrupt-cells = <3>;  
        reg = <0xf9000000 0x1000="">,  
              <0xf9002000 0x1000="">;  
    };  

    msmgpio: gpio@fd510000 {  
        compatible = "qcom,msm-gpio";  
        gpio-controller;  
        #gpio-cells = <2>;  
        interrupt-controller;  
        #interrupt-cells = <2>;  
        reg = <0xfd510000 0x4000="">;  
        ngpio = <146>;  
        interrupts = <0 208="" 0="">;  
        qcom,direct-connect-irqs = <8>;  
    };  

    wcd9xxx_intc: wcd9xxx-irq {  
        compatible = "qcom,wcd9xxx-irq";  
        interrupt-controller;  
        #interrupt-cells = <1>;  
        interrupt-parent = <&msmgpio>;  
        interrupts = <72 0="">;  
        interrupt-names = "cdc-int";  
    };  

    timer {  
        compatible = "arm,armv7-timer";  
        interrupts = <1 2="" 0="" 1="" 3="" 0="">;  
        clock-frequency = <19200000>;  
    };  

    i2c_0: i2c@f9967000 { /* BLSP#11 */  
        cell-index = <0>;  
        compatible = "qcom,i2c-qup";  
        reg = <0xf9967000 0x1000="">;  
        #address-cells = <1>;  
        #size-cells = <0>;  
        reg-names = "qup_phys_addr";  
        interrupts = <0 105="" 0="">;  
        interrupt-names = "qup_err_intr";  
        qcom,i2c-bus-freq = <100000>;  
        qcom,i2c-src-freq = <50000000>;  
    };  

    i2c_2: i2c@f9924000 {  
        cell-index = <2>;  
        compatible = "qcom,i2c-qup";  
        reg = <0xf9924000 0x1000="">;  
        #address-cells = <1>;  
        #size-cells = <0>;  
        reg-names = "qup_phys_addr";  
        interrupts = <0 96="" 0="">;  
        interrupt-names = "qup_err_intr";  
        qcom,i2c-bus-freq = <100000>;  
        qcom,i2c-src-freq = <50000000>;  
    };  

    spi_0: spi@f9923000 {  
        compatible = "qcom,spi-qup-v2";  
        reg = <0xf9923000 0x1000="">;  
        interrupts = <0 95="" 0="">;  
        spi-max-frequency = <19200000>;  
        #address-cells = <1>;  
        #size-cells = <0>;  
        gpios = <&msmgpio 3 0>, /* CLK  */  
            <&msmgpio 1 0>, /* MISO */  
            <&msmgpio 0 0>; /* MOSI */  
        cs-gpios = <&msmgpio 9 0>;  
    };  

};  

从上面可知,系统平台上挂载了很多总线,如i2c、spi、uart等等,每一个总线分别被描述为一个节点。Linux在启动后,到C入口时,会执行以下操作,加载系统平台上的总线和设备:
start_kernel() --> setup_arch() --> unflatten_device_tree()

在执行完unflatten_device_tree()后,DTS节点信息被解析出来,保存到allnodes链表中,allnodes会在后面被用到。
随后,当系统启动到board文件时,会调用.init_machine,高通8974平台对应的是msm8974_init()。接着调用of_platform_populate(....)接口,加载平台总线和平台设备。至此,系统平台上的所有已配置的总线和设备将被注册到系统中。注意:不是dtsi文件中所有的节点都会被注册,在注册总线和设备时,会对dts节点的状态作一个判断,如果节点里面的status属性没有被定义,或者status属性被定义了并且值被设为“ok”或者“okay”,其他情况则不被注册到系统中。

上面重复讲了Linux怎样使用DTS注册平台总线和平台设备到系统中,那么其他设备,例如i2c、spi设备是怎样注册到系统中的呢?下面我们就以i2c设备为例,讲解Linux怎样注册i2c设备到系统中。
以高通8974平台为例,在注册i2c总线时,会调用到qup_i2c_probe()接口,该接口用于申请总线资源和添加i2c适配器。在成功添加i2c适配器后,会调用of_i2c_register_devices()接口。此接口会解析i2c总线节点的子节点(挂载在该总线上的i2c设备节点),获取i2c设备的地址、中断号等硬件信息。然后调用request_module()加载设备对应的驱动文件,调用i2c_new_device(),生成i2c设备。此时设备和驱动都已加载,于是drvier里面的probe方法将被调用。后面流程就和之前一样了。

简而言之,Linux采用DTS描述设备硬件信息后,省去了大量板文件垃圾信息。Linux在开机启动阶段,会解析DTS文件,保存到全局链表allnodes中,在掉用.init_machine时,会跟据allnodes中的信息注册平台总线和设备。值得注意的是,加载流程并不是按找从树根到树叶的方式递归注册,而是只注册根节点下的第一级子节点,第二级及之后的子节点暂不注册。Linux系统下的设备大多都是挂载在平台总线下的,因此在平台总线被注册后,会根据allnodes节点的树结构,去寻找该总线的子节点,所有的子节点将被作为设备注册到该总线上。

2014-12-07 19:59:24 xiangshimoni 阅读数 3319

九、编译设备树dts为dtb

前面的linux内核源码目录中已经包含了设备树编译器Device Tree Compiler(dtc),在目录arch/arm/boot/dts/目录下。

将前面生成的xilinx.dts文件拷贝到ubuntu下,使用如下命令进行编译

dtc -I dts -O dtb -o devicetree.dtb xilinx.dts

上面Dtc编译器有几个选项和参数:

-I是输入文件格式,在本文中是dts

-O是目标文件格式,在本文中是dtb

-o是目标文件路径

最后一个参数是输入文件路径

编译之后会在目标文件路径生成一个dtb文件,这个文件名必须按照前面uboot默认配置中的文件进行命名,本文中是devicetree.dtb

 

至此,dtb文件生成完成。


十、制作根文件系统

为了发挥linux的强大功能,必须要挂载一个文件系统到linux上,嵌入式linux只挂载一个根文件系统,并不再挂载其他的文件系统。

根文件系统可以基于busybox进行制作,过程比较繁琐,本文不再赘述,读者可以在网络上查找资料。

为了正常进行实验,本文使用ZedBoard配套SD卡中自带的ramdisk8M.image.gz

读者可以使用gzip -d ramdisk8M.image.gz解压ramdisk镜像并挂载在某个目录下,在其中添加需要的功能后,再使用gizp -9 ramdisk8M.image 压缩回来。


2015-08-18 11:35:16 aa120515692 阅读数 5412

对于 MT7620a 。必须修改dts文件,否则编译出来的固件将无法启动:

vi target/linux/ramips/dts/MT7620a.dts

把 ethernet@10100000 和 gsw@10110000 修改成下面的内容:

        ethernet@10100000 {
                status = "okay";
                mtd-mac-address = <&factory 0x4>;

                pinctrl-names = "default";
                pinctrl-0 = <&ephy_pins>;

                ralink,port-map = "llllw";

        };

        gsw@10110000 {
                ralink,port4 = "ephy_pins";
        };
没有更多推荐了,返回首页