蜗窝科技

Device Tree(三):代码分析

linuxer · · 统一设备模型 · 阅读 174103 · 2014年6月存档

一、前言

Device Tree总共有三篇,分别是:

1、为何要引入Device Tree,这个机制是用来解决什么问题的?(请参考引入Device Tree的原因)

2、Device Tree的基础概念(请参考DT基础概念)

3、ARM linux中和Device Tree相关的代码分析(这是本文的主题)

本文主要内容是:以Device Tree相关的数据流分析为索引,对ARM linux kernel的代码进行解析。主要的数据流包括:

1、初始化流程。也就是扫描dtb并将其转换成Device Tree Structure。

2、传递运行时参数传递以及platform的识别流程分析

3、如何将Device Tree Structure并入linux kernel的设备驱动模型。

注:本文中的linux kernel使用的是3.14版本。

二、如何通过Device Tree完成运行时参数传递以及platform的识别功能?

1、汇编部分的代码分析

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代码进行处理。

2、和device tree相关的setup_arch代码分析

具体的c代码都是在setup_arch中处理,这个函数是一个总的入口点。具体代码如下(删除了部分无关代码):

void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;

……

mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;

……
}

对于如何确定HW platform这个问题,旧的方法是静态定义若干的machine描述符(struct machine_desc ),在启动过程中,通过machine type ID作为索引,在这些静态定义的machine描述符中扫描,找到那个ID匹配的描述符。在新的内核中,首先使用setup_machine_fdt来setup machine描述符,如果返回NULL,才使用传统的方法setup_machine_tags来setup machine描述符。传统的方法需要给出__machine_arch_type(bootloader通过r1寄存器传递给kernel的)和tag list的地址(用来进行tag parse)。__machine_arch_type用来寻找machine描述符;tag list用于运行时参数的传递。随着内核的不断发展,相信有一天linux kernel会完全抛弃tag list的机制。

3、匹配platform(machine描述符)

setup_machine_fdt函数的功能就是根据Device Tree的信息,找到最适合的machine描述符。具体代码如下:

const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;

if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
return NULL;

mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

if (!mdesc) {
出错处理
}

/* Change machine number to match the mdesc we’re using */
__machine_arch_type = mdesc->nr;

return mdesc;
}

early_init_dt_scan函数有两个功能,一个是为后续的DTB scan进行准备工作,另外一个是运行时参数传递。具体请参考下面一个section的描述。

of_flat_dt_match_machine是在machine描述符的列表中scan,找到最合适的那个machine描述符。我们首先看如何组成machine描述符的列表。和传统的方法类似,也是静态定义的。DT_MACHINE_START和MACHINE_END用来定义一个machine描述符。编译的时候,compiler会把这些machine descriptor放到一个特殊的段中(.arch.info.init),形成machine描述符的列表。machine描述符用下面的数据结构来标识(删除了不相关的member):

struct machine_desc {
unsigned int nr; /* architecture number */
const char *const *dt_compat; /* array of device tree ‘compatible’ strings */

……

};

nr成员就是过去使用的machine type ID。内核machine描述符的table有若干个entry,每个都有自己的ID。bootloader传递了machine type ID,指明使用哪一个machine描述符。目前匹配machine描述符使用compatible strings,也就是dt_compat成员,这是一个string list,定义了这个machine所支持的列表。在扫描machine描述符列表的时候需要不断的获取下一个machine描述符的compatible字符串的信息,具体的代码如下:

static const void * __init arch_get_next_mach(const char *const **match)
{
static const struct machine_desc *mdesc = __arch_info_begin;
const struct machine_desc *m = mdesc;

if (m >= __arch_info_end)
return NULL;

mdesc++;
*match = m->dt_compat;
return m;
}

__arch_info_begin指向machine描述符列表第一个entry。通过mdesc++不断的移动machine描述符指针(Note:mdesc是static的)。match返回了该machine描述符的compatible string list。具体匹配的算法倒是很简单,就是比较字符串而已,一个是root node的compatible字符串列表,一个是machine描述符的compatible字符串列表,得分最低的(最匹配的)就是我们最终选定的machine type。

4、运行时参数传递

运行时参数是在扫描DTB的chosen node时候完成的,具体的动作就是获取chosen node的bootargs、initrd等属性的value,并将其保存在全局变量(boot_command_line,initrd_start、initrd_end)中。使用tag list方法是类似的,通过分析tag list,获取相关信息,保存在同样的全局变量中。具体代码位于early_init_dt_scan函数中:

bool __init early_init_dt_scan(void *params)
{
if (!params)
return false;

/* 全局变量initial_boot_params指向了DTB的header*/
initial_boot_params = params;

/* 检查DTB的magic,确认是一个有效的DTB */
if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) {
initial_boot_params = NULL;
return false;
}

/* 扫描 /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);

return true;
}

设定meminfo(该全局变量确定了物理内存的布局)有若干种途径:

1、通过tag list(tag是ATAG_MEM)传递memory bank的信息。

2、通过command line(可以用tag list,也可以通过DTB)传递memory bank的信息。

3、通过DTB的memory node传递memory bank的信息。

目前当然是推荐使用Device Tree的方式来传递物理内存布局信息。

三、初始化流程

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

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

/* Get pointer to “/chosen” and “/aliases” nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
}

我们用struct device_node 来抽象设备树中的一个节点,具体解释如下:

struct device_node {
const char *name;----------------------device node name
const char *type;-----------------------对应device_type的属性
phandle phandle;-----------------------对应该节点的phandle属性
const char *full_name; ----------------从“/”开始的,表示该node的full path

struct property *properties;-------------该节点的属性列表
struct property *deadprops; ----------如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表
struct device_node *parent;------parent、child以及sibling将所有的device node连接起来
struct device_node *child;
struct device_node *sibling;
struct device_node *next; --------通过该指针可以获取相同类型的下一个node
struct device_node *allnext;-------通过该指针可以获取node global list下一个node
struct proc_dir_entry *pde;--------开放到userspace的proc接口信息
struct kref kref;-------------该node的reference count
unsigned long _flags;
void *data;
};

unflatten_device_tree函数的主要功能就是扫描DTB,将device node被组织成:

1、global list。全局变量struct device_node *of_allnodes就是指向设备树的global list

2、tree。

这些功能主要是在__unflatten_device_tree函数中实现,具体代码如下(去掉一些无关紧要的代码):

static void __unflatten_device_tree(struct boot_param_header *blob,---需要扫描的DTB
struct device_node **mynodes,---------global list指针
void * (*dt_alloc)(u64 size, u64 align))------内存分配函数
{
unsigned long size;
void *start, *mem;
struct device_node **allnextp = mynodes;

此处删除了health check代码,例如检查DTB header的magic,确认blob的确指向一个DTB。

/* scan过程分成两轮,第一轮主要是确定device-tree structure的长度,保存在size变量中 */
start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
size = ALIGN(size, 4);

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

*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); //用来检验后面unflattening是否溢出

/* 这是第二轮的scan,第一次scan是为了得到保存所有node和property所需要的内存size,第二次就是实打实的要构建device node tree了 */
start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);

此处略去校验溢出和校验OF_DT_END。
}

具体的scan是在unflatten_dt_node函数中,如果已经清楚地了解DTB的结构,其实代码很简单,这里就不再细述了。

四、如何并入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。导致Device Tree的引入ARM体系结构的代码其中一个最重要的原因的太多的静态定义的表格。例如:一般代码中会定义一个static struct platform_device *xxx_devices的静态数组,在初始化的时候调用platform_add_devices。这些静态定义的platform_device往往又需要静态定义各种resource,这导致静态表格进一步增大。如果ARM linux中不再定义这些表格,那么一定需要一个转换的过程,也就是说,系统应该会根据Device tree来动态的增加系统中的platform_device。当然,这个过程并非只是发生在platform bus上(具体可以参考“Platform Device”的设备),也可能发生在其他的非即插即用的bus上,例如AMBA总线、PCI总线。一言以蔽之,如果要并入linux kernel的设备驱动模型,那么就需要根据device_node的树状结构(root是of_allnodes)将一个个的device node挂入到相应的总线device链表中。只要做到这一点,总线机制就会安排device和driver的约会。

当然,也不是所有的device node都会挂入bus上的设备链表,比如cpus node,memory node,choose node等。

1、cpus node的处理

这部分的处理可以参考setup_arch->arm_dt_init_cpu_maps中的代码,具体的代码如下:

void __init arm_dt_init_cpu_maps(void)
{
scan device node global list,寻找full path是“/cpus”的那个device node。cpus这个device node只是一个容器,其中包括了各个cpu node的定义以及所有cpu node共享的property。
cpus = of_find_node_by_path("/cpus");

for_each_child_of_node(cpus, cpu) { 遍历cpus的所有的child node
u32 hwid;

if (of_node_cmp(cpu->type, “cpu”)) 我们只关心那些device_type是cpu的node
continue;

if (of_property_read_u32(cpu, “reg”, &hwid)) { 读取reg属性的值并赋值给hwid
return;
}

reg的属性值的8 MSBs必须设置为0,这是ARM CPU binding定义的。
if (hwid & ~MPIDR_HWID_BITMASK)
return;

不允许重复的CPU id,那是一个灾难性的设定
for (j = 0; j < cpuidx; j++)
if (WARN(tmp_map[j] == hwid, “Duplicate /cpu reg "
“properties in the DT\n”))
return;

数组tmp_map保存了系统中所有CPU的MPIDR值(CPU ID值),具体的index的编码规则是: tmp_map[0]保存了booting CPU的id值,其余的CPU的ID值保存在1~NR_CPUS的位置。
if (hwid == mpidr) {
i = 0;
bootcpu_valid = true;
} else {
i = cpuidx++;
}

tmp_map[i] = hwid;
}

根据DTB中的信息设定cpu logical map数组。

for (i = 0; i < cpuidx; i++) {
set_cpu_possible(i, true);
cpu_logical_map(i) = tmp_map[i];
}
}

要理解这部分的内容,需要理解ARM CUPs binding的概念,可以参考linux/Documentation/devicetree/bindings/arm目录下的CPU.txt文件的描述。

2、memory的处理

这部分的处理可以参考setup_arch->setup_machine_fdt->early_init_dt_scan->early_init_dt_scan_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_type属性值
__be32 *reg, *endp;
unsigned long l;

在初始化的时候,我们会对每一个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, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®);

early_init_dt_add_memory_arch(base, size); 将具体的memory block信息加入到内核中。
}

return 0;
}

3、interrupt controller的处理

初始化是通过start_kernel->init_IRQ->machine_desc->init_irq()实现的。我们用S3C2416为例来描述interrupt controller的处理过程。下面是machine描述符的定义。

DT_MACHINE_START(S3C2416_DT, “Samsung S3C2416 (Flattened Device Tree)”)
……
.init_irq = irqchip_init,
……
MACHINE_END

在driver/irqchip/irq-s3c24xx.c文件中定义了两个interrupt controller,如下:

IRQCHIP_DECLARE(s3c2416_irq, “samsung,s3c2416-irq”, s3c2416_init_intc_of);

IRQCHIP_DECLARE(s3c2410_irq, “samsung,s3c2410-irq”, s3c2410_init_intc_of);

当然,系统中可以定义更多的irqchip,不过具体用哪一个是根据DTB中的interrupt controller node中的compatible属性确定的。在driver/irqchip/irqchip.c文件中定义了irqchip_init函数,如下:

void __init irqchip_init(void)
{
of_irq_init(__irqchip_begin);
}

__irqchip_begin就是所有的irqchip的一个列表,of_irq_init函数是遍历Device Tree,找到匹配的irqchip。具体的代码如下:

void __init of_irq_init(const struct of_device_id *matches)
{
struct device_node *np, *parent = NULL;
struct intc_desc *desc, *temp_desc;
struct list_head intc_desc_list, intc_parent_list;

INIT_LIST_HEAD(&intc_desc_list);
INIT_LIST_HEAD(&intc_parent_list);

遍历所有的node,寻找定义了interrupt-controller属性的node,如果定义了interrupt-controller属性则说明该node就是一个中断控制器。

for_each_matching_node(np, matches) {
if (!of_find_property(np, “interrupt-controller”, NULL) ||
!of_device_is_available(np))
continue;

分配内存并挂入链表,当然还有根据interrupt-parent建立controller之间的父子关系。对于interrupt controller,它也可能是一个树状的结构。
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
if (WARN_ON(!desc))
goto err;

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);-----匹配并初始化
if (WARN(!match->data,----------match->data是初始化函数
“of_irq_init: no init function for %s\n”,
match->compatible)) {
kfree(desc);
continue;
}

irq_init_cb = (of_irq_init_cb_t)match->data;
ret = irq_init_cb(desc->dev, desc->interrupt_parent);-----执行初始化函数
if (ret) {
kfree(desc);
continue;
}

处理完的节点放入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_or_null(&intc_parent_list,
typeof(*desc), list);
if (!desc) {
pr_err(“of_irq_init: children remain, but no parents\n”);
break;
}
list_del(&desc->list);
parent = desc->dev;
kfree(desc);
}

list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
list_del(&desc->list);
kfree(desc);
}
err:
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
list_del(&desc->list);
kfree(desc);
}
}

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

OK,我们已经通过compatible属性找到了适合的interrupt controller,那么如何解析reg属性呢?我们知道,对于s3c2416的interrupt controller而言,其#interrupt-cells的属性值是4,定义为。每个域的解释如下:

(1)ctrl_num表示使用哪一种类型的interrupt controller,其值的解释如下:

  • 0 … main controller
  • 1 … sub controller
  • 2 … second main controller

(2)parent_irq。对于sub controller,parent_irq标识了其在main controller的bit position。

(3)ctrl_irq标识了在controller中的bit位置。

(4)type标识了该中断的trigger type,例如:上升沿触发还是电平触发。

为了更顺畅的描述后续的代码,我需要简单的介绍2416的中断控制器,其block diagram如下:

2416intc

53个Samsung2416的中断源被分成两种类型,一种是需要sub寄存器进行控制的,例如DMA,系统中的8个DMA中断是通过两级识别的,先在SRCPND寄存器中得到是DMA中断的信息,具体是哪一个channel的DMA中断需要继续查询SUBSRC寄存器。那些不需要sub寄存器进行控制的,例如timer,5个timer的中断可以直接从SRCPND中得到。
中断MASK寄存器可以控制产生的中断是否要报告给CPU,当一个中断被mask的时候,虽然SRCPND寄存器中,硬件会set该bit,但是不会影响到INTPND寄存器,从而不会向CPU报告该中断。对于SUBMASK寄存器,如果该bit被set,也就是该sub中断被mask了,那么即便产生了对应的sub中断,也不会修改SRCPND寄存器的内容,只是修改SUBSRCPND中寄存器的内容。

不过随着硬件的演化,更多的HW block加入到SOC中,这使得中断源不够用了,因此中断寄存器又被分成两个group,一个是group 1(开始地址是0X4A000000,也就是main controller了),另外一个是group2(开始地址是0X4A000040,叫做second main controller)。group 1中的sub寄存器的起始地址是0X4A000018(也就是sub controller)。

了解了上面的内容后,下面的定义就比较好理解了:

static struct s3c24xx_irq_of_ctrl s3c2416_ctrl[] = {
{
.name = “intc”, -----------main controller
.offset = 0,
}, {
.name = “subintc”, ---------sub controller
.offset = 0x18,
.parent = &s3c_intc[0],
}, {
.name = “intc2”, ----------second main controller
.offset = 0x40,
}
};

对于s3c2416而言,irqchip的初始化函数是s3c2416_init_intc_of,s3c2416_ctrl作为参数传递给了s3c_init_intc_of,大部分的处理都是在s3c_init_intc_of函数中完成的,由于这个函数和中断子系统非常相关,这里就不详述了,后续会有一份专门的文档描述之。

4、GPIO controller的处理

暂不描述,后续会有一份专门的文档描述GPIO sub system。

5、machine初始化

machine初始化的代码可以沿着start_kernel->rest_init->kernel_init->kernel_init_freeable->do_basic_setup->do_initcalls路径寻找。在do_initcalls函数中,kernel会依次执行各个initcall函数,在这个过程中,会调用customize_machine,具体如下:

static int __init customize_machine(void)
{

if (machine_desc->init_machine)
machine_desc->init_machine();
else
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);

return 0;
}
arch_initcall(customize_machine);

在这个函数中,一般会调用machine描述符中的init_machine callback函数来把各种Device Tree中定义的platform device设备节点加入到系统(即platform bus的所有的子节点,对于device tree中其他的设备节点,需要在各自bus controller初始化的时候自行处理)。如果machine描述符中没有定义init_machine函数,那么直接调用of_platform_populate把所有的platform device加入到kernel中。对于s3c2416,其machine描述符中的init_machine callback函数就是s3c2416_dt_machine_init,代码如下:

static void __init s3c2416_dt_machine_init(void)
{
of_platform_populate(NULL, ——–传入NULL参数表示从root node开始scan

of_default_bus_match_table, s3c2416_auxdata_lookup, NULL);

s3c_pm_init(); --------power management相关的初始化
}

由此可见,最终生成platform device的代码来自of_platform_populate函数。该函数的逻辑比较简单,遍历device node global list中所有的node,并调用of_platform_bus_create处理,of_platform_bus_create函数代码如下:

static int of_platform_bus_create(struct device_node *bus,————-要创建的那个device node
const struct of_device_id *matches,——-要匹配的list
const struct of_dev_auxdata *lookup,——附属数据
struct device *parent, bool strict)—————parent指向父节点。strict是否要求完全匹配
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;

删除确保device node有compatible属性的代码。

auxdata = of_dev_lookup(lookup, bus); 在传入的lookup table寻找和该device node匹配的附加数据
if (auxdata) {
bus_id = auxdata->name;—————–如果找到,那么就用附加数据中的静态定义的内容
platform_data = auxdata->platform_data;
}

ARM公司提供了CPU core,除此之外,它设计了AMBA的总线来连接SOC内的各个block。符合这个总线标准的SOC上的外设叫做ARM Primecell Peripherals。如果一个device node的compatible属性值是arm,primecell的话,可以调用of_amba_device_create来向amba总线上增加一个amba device。

if (of_device_is_compatible(bus, “arm,primecell”)) {
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}

如果不是ARM Primecell Peripherals,那么我们就需要向platform bus上增加一个platform device了

dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;

一个device node可能是一个桥设备,因此要重复调用of_platform_bus_create来把所有的device node处理掉。

for_each_child_of_node(bus, child) {
pr_debug(” create child: %s\n", child->full_name);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
return rc;
}

具体增加platform device的代码在of_platform_device_create_pdata中,代码如下:

static struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;

if (!of_device_is_available(np))———check status属性,确保是enable或者OK的。
return NULL;

of_device_alloc除了分配struct platform_device的内存,还分配了该platform device需要的resource的内存(参考struct platform_device 中的resource成员)。当然,这就需要解析该device node的interrupt资源以及memory address资源。

dev = of_device_alloc(np, bus_id, parent);
if (!dev)
return NULL;

设定platform_device 中的其他成员
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;

if (of_device_add(dev) != 0) {——————把这个platform device加入统一设备模型系统中
platform_device_put(dev);
return NULL;
}

return dev;
}

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。

标签:设备树

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(162 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • 建站技巧

    很好  学习了  谢谢分享
  • Daniel Shieh

    这部分的内容感觉好复杂啊,如果仅仅是简单的使用device tree来配置已有驱动,需要修改对应的dts文件中的内容,然后编译,再放回就可以了吧?这些文件一般都是在哪里呢?是如何被内核动态加载的呢?
    • linuxer

      @Daniel Shieh:一些示例性的dts文件位于linux/arch/arm/boot/dts目录下,修改完毕后可以使用dtc来进行编译,dtc的source code位于linux/scripts/dtc。编译之后编程dtb文件。kernel的image中不会包含dtb的内容,具体dtb放在哪里是和bootloader相关的,可以放在NAND FLASH上,这样,bootloader在load OS之前要向从NAND FLASH中加载dtb文件到SDRAM中,然后把控制权交给kernel,kernel不会加载dtb文件的,kernel只是分析dtb而已。
      • Daniel Shieh

        @linuxer:我用的板子是BeagleboneBlack,从SD卡启动,dtb文件是编译内核后生成的,然后解压拷贝到uboot所在的SD卡上。这些应该都是没问题的。启动的时候,最后报错的信息就是下面这个函数中的错误处理,你觉得可能是什么错误呢? const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) { const struct machine_desc *mdesc, *mdesc_best = NULL; if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys))) return NULL; mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); if (!mdesc) { 出错处理 } /* Change machine number to match the mdesc we're using */ __machine_arch_type = mdesc->nr; return mdesc; }
        • linuxer

          @Daniel Shieh:这一定是匹配问题了,可以用下面的步骤debug: 1、反编译你使用的dtb文件,查看root node的compatible是什么? 2、DUMP linux kernel ELF文件,看看.arch.info.init这个section中的内容。这个section中一定要有和root node的compatible相同的字符串
          • Daniel Shieh

            @linuxer:readelf vmlinux信息,出来下面这个,怎么能查看这段的字符信息呢? [21] .init.arch.info PROGBITS c07bc6ac 7c46ac 0002f4 00 A 0 0 4 这的确是匹配问题,会不会是uboot传递的参数信息中有device tree的相关的compatible不在内核比对的那个列表里?uboot都传递哪些参数给kernel呢?
            • linuxer

              @Daniel Shieh:如果你是ARM平台,建议用arm-linux-objcopy这个命令把.init.arch.info这个segment从linux kernel的elf文件中copy出来,形成一个文件,然后用UltraEdit或者其他的可以看文本和binary的工具阅读文件即可。
  • lg2841

    多谢您的三篇精彩文章,终于算是大概将整个基于device tree的启动加载搞清楚了。
    • linuxer

      @lg2841:不必客气。那怕就是能够帮助一个对linux内核有兴趣的人我就满足了,呵呵~~~~~
  • 问问

    神一般的人物
  • 竹萃集

    这部分的内容感觉好复杂啊,如果仅仅是简单的使用device tree来配置已有驱动,需要修改对应的dts文件中的内容,然后编译,再放回就可以了吧?这些文件一般都是在哪里呢?是如何被内核动态加载的呢?
    • linuxer

      @竹萃集:你提的问题和下面的Daniel Shieh 网友提出的问题类似,请参考下面我的回答,多谢!
  • perr

    试图通过dtb启动,遇到以下问题.请求站长给看看 http://www.21qa.net/questions/713/713
    • linuxer

      @perr:抱歉,实际工作中我也没有使用过device tree,我只能给你一些思考的方向: 1、是不是在数据传递路径上有些代码错误的访问了DTB block的数据。数据完整性检查的点包括: (1)dts编译阶段,产生dtb文件 (2)bootloader阶段,0x20000100位置的数据 (3)kernel执行阶段,setup_machine_fdt之前,dump0xc0000100位置的数据 如果这三个阶段dump出来的数据都是一样的话,那么dtb在数据copy过程中没有问题 2、编译过程和kernel中的解析过程中,对dtb的格式理解不一致,例如版本什么的
  • Mr_White

    我想请问新版的内核如果不用device tree,把以前可用的uboot和新版的uImage放到系统中可以正常启动吗?
    • linuxer

      @Mr_White:有些arch已经强制使用device tree了,例如ARM64,如果你的硬件平台式ARM64的当然不行。如果是ARM 32bit的平台,当然可以不使用device tree,这时候,uboot仍然传递旧的tag list给kernel,kernel解析之并可以正常启动。
      • Mr_White

        @linuxer:我试着移植新内核到AM3352上,但是用之前的uboot,内核总是起不来,我想请问在make menuconfig的时候,需要如何配置才能使内核不使用device tree?我看到Boot options中的第一个选项“Flattened Device Tree support”是强制选中的
        • Mr_White

          @Mr_White:内核版本是3.12
        • wowo

          @Mr_White:U-Boot tags传递是否正确?kernel machine的配置是否正确? 就算kernel配置为使用device tree,一样可以通过旧的方式加载(从DTS读取不成功的话),因此你不用太在意是否使能device tree功能。 另外,“Flattened Device Tree support”是强制选中的,说明有Kconfig(如arch相关的)主动选择了这个配置(select USE_OF),你可以去kernel中查一下。
  • pingchangxin

    写的太好了,我又一个疑问,以前版本上在一些i2c/spi注册一个设备的时候,有一些board_info的注册,例如spi_register_board_info,把每个spi_board_info挂在全局链表变量board_list上,并且遍历已经在系统中注册了的控制器,匹配上相应的控制器并取得它们的spi_master结构指针,最终也会通过spi_new_device函数添加SPI设备,现在用device tree是怎么完成这个操作的呢?谢谢!
    • wowo

      @pingchangxin:board info和device tree之间没有任何关系的,device tree只是描述SPI/I2C platform相关的信息(寄存器映射、中断等等),而board info,还是需要在board的初始化代码中提供。 可参考如下表述(Documentation/devicetree/bindings/spi/spi-bus.txt): However, the binding does not attempt to define the specific method for assigning chip select numbers. Since SPI chip select configuration is flexible and non-standardized, it is left out of this binding with the assumption that board specific platform code will be used to manage chip selects.
      • study

        @wowo:@wowo 在您的第一篇文章中提到: “ 5、ARM SOC board specific的代码被移除,由Device Tree机制来负责传递硬件拓扑和硬件资源信息。 ”这一句话,既然Device Tree可以传递硬件资源,那么就不需要spi_board_info, i2c_board_info信息了,是吗? 这点有点疑惑。
        • wowo

          @study:我们可以从两个角度看这个问题: 1. device tree的本质。 没错,device tree的目的是抽象、描述硬件,但是,抽象、描述硬件并不是最终目标。最终目标是:“通过修改device tree,方便的支持不同的平台、版型,从而不需要重新编译kernel”。 要实现这个最终目标,device tree所描述的内容要具备哪些基本条件?共性!回想一下device tree的基本内容:属性=值。因此所谓的共性,就是要有相同的属性,通过修改属性值,达到支持不同平台、版型的目的,例如reg、irq、gpio、reset、power gate等等。 2. device tree的层次结构。 这里的层次,基本上和总线拓扑的层次是一致的。越顶层,“共性”的可能性越大;越底层,则越小。因此对kernel的原生实现来说,就不可能事无巨细的定义每一层的device tree内容。但这不代表我们不能使用device tree(对应您问题中的一段表述),具体参考下面描述。 我们以SPI为例,kernel framework能抽象出来哪些共同的属性放到device tree里面呢?参考“Documentation/devicetree/bindings/spi/spi-bus.txt”,是不多的。 kernel无法假设具体的SPI BUS的更为细致的特性(就是那些在spi_board_info中描述的内容),因而无法提供标准的device tree 属性值,它能做的,就是定义一个数据结构,告诉各个spi bus driver(你自己提供、自己使用哦)。 那么问题来了,可不可以把spi_board_info的内容也放到DTS中?当然可以,我们可以在spi节点下定义一个子节点,放入board info的一些内容,然后在probe中,解析出来,放到spi_board_info中。但要记住,这个子节点,只对某一特定平台有效。
          • study

            @wowo:谢谢您的解惑.
  • tigger

    Dts 里面有一个比较难懂的地方就是range 社区标准的每个节点都有range 但是大部分range都是空的因为 那个节点的地址就是cpu的视角 但是当这个节点的父节点是一个external bus 的话 就肯能要用range了因为挂在external bus上面的设备的寄存器地址可能通过cpu不能直接访问,只能通过bus的domain来解析。这样的话,也必须得自己写一个标准的bus driver来解析bus上的各个节点。 因为如果你讲这个bus上的各个节点的地址直接ioremap成虚拟地址,那他们很这个bus 就不会有直接的关系。 楼主 我说的没错吧?虽然有点啰嗦 哈哈
    • linuxer

      @tigger:我以前没有留意ranges这个属性,我特地又重新看看了ranges属性的定义。 ranges属性定义了bus节点的地址空间和bus节点的父节点的地址空间的映射关系。例如: CPU ------- pci A------pci B----pci device PCI B这个节点是一个桥设备,它是PCI A总线上的一个device,同时又延展出了一个PCI总线。对于PCI B,其父节点就是PCI A。对于PCI B这个节点,我们可以定义ranges属性,将PCI B的总线地址映射到PCI A的总线地址上去
      • tigger

        @linuxer:我最近debug就发现了一个问题,如果比如PCI B这个总线drvier没有注册,那么在PCI B总线上的device是不会probe的。另外如果是PCI这种公共的总线还好,要是一款新品有一个自己特定的总线,那么写起来还是比较麻烦的。要自己写一个bus的driver
        • linuxer

          @tigger:如果parent没有正常初始化,下面的各个child device是不肯能正确初始化的,毕竟对child device的访问是需要通过parent的。总线驱动还是比较复杂的,还好我们只需要理解就OK了。
          • tigger

            @linuxer:ok i see
  • 足球男孩

    您好,我现在用S5PV210系列开发板用设备树移植3.18.2最新的内核,内核也可以识别 内核运行到void __init setup_arch(char **cmdline_p)这函数,能够正常识别设备树,但是在运行unflatten_device_tree() ----》 __unflatten_device_tree(initial_boot_params, &of_allnodes, early_init_dt_alloc_memory_arch); --》 static void * unflatten_dt_node(void *blob, void *mem, int *poffset, struct device_node *dad, struct device_node ***allnextpp, unsigned long fpsize) 这是解析扁平树的函数,但是在unflatten_dt_node()函数就卡死了,是不是递归卡死了,还是什么问题呢?
    • wowo

      @足球男孩:加几行打印,就能找到为什么卡死了。
      • 足球男孩

        @wowo:请问你研究过unflatten_dt_node()这个函数么? 这个函数里的一段代码:if (allnextpp) { *prev_pp = NULL; np->name = of_get_property(np, "name", NULL); np->type = of_get_property(np, "device_type", NULL); if (!np->name) np->name = "<NULL>"; if (!np->type) np->type = "<NULL>"; } 其中在np->name = of_get_property(np, "name", NULL);这个函数里卡死了! 然后一直跟进函数,发现就在这个函数卡死:static struct property *__of_find_property(const struct device_node *np, const char *name, int *lenp) { struct property *pp; if (!np) return NULL; for (pp = np->properties; pp; pp = pp->next) { if (of_prop_cmp(pp->name, name) == 0) { if (lenp) *lenp = pp->length; break; } } return pp; } 根据打印输出显示,卡死在这句of_prop_cmp(pp->name, name) == 0,,这句其实就是调用strcmp()函数一直比较,可是却无端地卡死了,我一直找不到问题。 是不是解析扁平树一直递归,导致栈满溢出? 还是另有原因,期待你的帮助。谢谢
        • wowo

          @足球男孩:"卡死在这句of_prop_cmp(pp->name, name) == 0",能确定是停在这里不动了?还是在for里面死循环? 由于执行cmp的时候,调用了raw_spin_lock_irqsave,就算是死循环,也看不到打印。 如果是直接不动了,会不会是DDR的问题?降频试试? 如果是死循环,就要查查np->properties,是不是被意外覆盖了? 如果怀疑栈满,把堆栈打出来看看。
          • 足球男孩

            @wowo:就是卡住不动了,我不知道如何打印堆栈, 你说的这些ddr,降频 这些我不知道该如何操作了,请问可以再给点提示么?
            • wowo

              @足球男孩:我觉得问题出在strcmp上的概率大一些,of_prop_cmp调用前,能不能打印那两个名字? 至于堆栈信息,内核有一个dump_stack函数,不过输出比较多。最好是自己用汇编获得栈指针(SP)并打印出来。 如果是规律的死在一个位置,DDR的可能性不是很大。
              • 足球男孩

                @wowo:enen,谢谢,
  • yin262

    老师你好,我最近刚接触device tree,看了你的文章以后受益很多。 同时有些问题想请教一下,我在看linux/Documentation/devicetree/bindings/arm/cpus.txt文档的时候发现如下描述: - reg: property matching the CPU MPIDR[23:0] register bits reg[31:24] bits must be set to 0 这里的MPIDR应该是一个寄存器,但是我之前从来没有听说过ARM CPU中存在这样的寄存器,请问MPIDR的作用是什么呢?或者从哪里可以找到相关的资料了解呢?
  • callme_friend

    接触linux快两年,却还是在入门中,看到博主接触5年就有如此深度,真心佩服! 设备树三篇博文看完,再结合我板子上内核的打印信息,总算能理解大概了,非常感谢博文。 请问博主:Device tree(二)中的基本概念的内容,是哪里找到的?
    • linuxer

      @callme_friend:我估计你搞错了,我不是wowo同学,那位帅哥接触linux有5、6年了,我大概有15年了,不过这和接触linux有多久没有关系,device tree是一个比较新的内容。其实很多文档我自己重新阅读都觉得很不理想,这三篇device tree的文档很粗糙,需要打磨,不过没太有时间去这么做。 学习linux的过程比较辛苦,尤其是前几年,熬过2~3年,整个世界都会不一样了......加油 所有的资料都是来自网络,不过我有看英文资料的习惯,当前阶段下,英文是掌握技术的利器,没有之一
      • callme_friend

        @linuxer:15年,真是很长很长的时间了。谢谢前辈鼓励,希望2-3年后会茅塞顿开,呵呵 前辈说的看英文资料,是有固定专门网站,还是零散google搜索后的文章? 再次感谢。
        • linuxer

          @callme_friend:其实google的资料并不多,我主要参考资料如下: 1、内核代码以及内核文档 2、Power_ePAPR_APPROVED_v1.1.pdf。我在文章中已经提及这份文档了。
          • callme_friend

            @linuxer:谢谢,我去看看文档!
  • printk

    问一个简单的问题。。。。。。。trampoline code是什么意思 。。。。。 困扰了我很久了。。根本翻译不出来阿。难道是蹦床代码吗,,求指教,这个单词是在看device tree的 文档时碰见的 原句是 : If your platform uses a real implementation of Open Firmware or an implementation compatible with the Open Firmware client interface, those properties will be created by the trampoline code in the kernel's prom_init() file.
    • wowo

      @printk:从维基上帮您抄回来,不翻译了: Trampolines (sometimes referred to as indirect jump vectors) are memory locations holding addresses pointing to interrupt service routines, I/O routines, etc. Execution jumps into the trampoline and then immediately jumps out, or bounces, hence the term trampoline. http://en.wikipedia.org/wiki/Trampoline_(computing)
  • zlf

    绝对好文章,多谢楼主分享知识的精神真是大赞! 心情急切等待 Device Tree (四)GPIO controller的处理
  • 蒸汽机车

    我用的是高通平台。 我刚看第一遍,有点困惑,dtb是bootloader阶段准备,那dtb的来源会是哪里,bootloader并没有找到描述文件,只有kernel源码里有。 又看了一遍,结合源码看,bootloader阶段去加载分区boot(kernel + ramdisk + device tree),ramdisk后面就是device tree,在这里得到dtb; dtb的应该是在编译boot镜像时候生成的吧,写到flash去;bootloader阶段读取flash再把dtb传给kernel。可以这样理解吗?
    • linuxer

      @蒸汽机车:你的问题可以在Device Tree(二):基本概念中的前言得到答案。
      • 蒸汽机车

        @linuxer:嗯,看了(二),过程和理解一样,我表述不太好。 没有引入设备树是这样准备硬件资源的在arch/arm/mach-xxx下准备 platform_device, i2c_board_info。 引入Device Tree不是也是类似在arch/arm/boot/dts/下准备硬件资源,只是换了一种写法。这样linus不是也得维护这些代码。 谢谢~~
        • linuxer

          @蒸汽机车:arch/arm/boot/dts/目录中的文件不是kernel image的中一部分,而没有引入设备树之前,硬件资源是以静态表格的形式hard code在kernel中,是kernel image的一部分。 在引入Device Tree之后,linus维护是固定不变的device tree代码,而不是arch/arm/boot/dts/目录中的文件,那些是传递给kernel的参数而已。
          • 蒸汽机车

            @linuxer:boot.img包括kernel,ramdisk,device tree三部分,device tree不在kernel image内,和楼主说的一样。以下的代码可以印证。 int boot_linux_from_mmc(void) { index = partition_get_index("boot"); ptn = partition_get_offset(index); imagesize_actual = (page_size + kernel_actual + ramdisk_actual + dt_actual); mmc_read(ptn, (void *)image_addr, imagesize_actual); boot_linux(....); return 0; } 谢谢楼主。
          • 蒸汽机车

            @linuxer:之前把boot和kernel镜像两个混淆了,以为是一样的
  • odriver

    楼主您好!感谢您的分享。请问dtb的反编译命令是什么?
    • wowo

      @odriver:DTS的编译和反编译命令都是dtc,通过-I和-O命令,可以在几种格式之间转换,如: dtc -I [dts,dtb,fs] -O [dts,dtb,asm] input_file 对反编译来说,就是 dtc -I dtb -O dts xxx.dtb 具体可参考dtc -h
      • odriver

        @wowo:谢谢前辈,这里简直就是linux内核驱动的学习圣地呀!
        • wowo

          @odriver:不用客气,欢迎常来。
          • tastier

            @wowo:wowo大神,请问反编译出来的dts文件可以直接用来生成dtb文件吗?
            • wowo

              @tastier:可以,命令反过来就可以了。
              • tastier

                @wowo:一些参考标签在反编译的DTS文件中显示为数字,这个没有影响吗?
  • ~零~

    博主,dst与dtsi之间有着包含关系,想请问下kernel是怎么确定第一个dst文件的名字的呢。
    • linuxer

      @~零~:dts文件描述了target board的硬件拓扑,是程序员自己需要创建的,只不过,很多target board的硬件资源是相同的(例如使用同样的ARM SOC),因此可以抽象出来dtsi文件,可以让不同的target board的dts文件可以include dtsi,从而减少重复定义。 kernel不需要确定dts文件,bootloader会把编译好的dtb文件传递给kernel的
      • ~零~

        @linuxer:假如说我想将原来的dts与dtsi重新命名并保留原来的文件,怎么样kernel使用到我新命名的文件呢
  • xiaopohaibebo

    你好,我咋arc/arm目录下有发现比较设备树和段中的 compatible 代码,但是在arch/arm64的 setup。c文件里完全没有 做arm下的setup。c函数中的匹配。想请教哈 为啥arm和arm64的 匹配设备代码 部分是不同的,那arm64如何做板级匹配呢?
    • linuxer

      @xiaopohaibebo:你可否更细致的描述你的问题?例如可以贴上一些代码来进行具体说明,我不是很了解你想要问的问题
      • @ linuxer

        @linuxer:这是 arm64的 static void __init setup_machine_fdt(phys_addr_t dt_phys) { if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys))) { early_print("\n" "Error: invalid device tree blob at physical address 0x%p (virtual address 0x%p)\n" "The dtb must be 8-byte aligned and passed in the first 512MB of memory\n" "\nPlease check your bootloader.\n", dt_phys, phys_to_virt(dt_phys)); while (true) cpu_relax(); } machine_name = of_flat_dt_get_machine_name(); if (machine_name) pr_info("Machine: %s\n", machine_name); } 下面是arm的 const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) { struct boot_param_header *devtree; const struct machine_desc *mdesc, *mdesc_best = NULL; unsigned int score, mdesc_score = ~1; unsigned long dt_root; const char *model; #ifdef CONFIG_ARCH_MULTIPLATFORM DT_MACHINE_START(GENERIC_DT, "Generic DT based system") MACHINE_END mdesc_best = &__mach_desc_GENERIC_DT; #endif if (!dt_phys) return NULL; devtree = phys_to_virt(dt_phys); /* check device tree validity */ if (be32_to_cpu(devtree->magic) != OF_DT_HEADER) return NULL; /* Search the mdescs for the 'best' compatible value match */ initial_boot_params = devtree; dt_root = of_get_flat_dt_root(); for_each_machine_desc(mdesc) { score = of_flat_dt_match(dt_root, mdesc->dt_compat); if (score > 0 && score < mdesc_score) { mdesc_best = mdesc; mdesc_score = score; } } ............ } setup_machine_fdt函数完全不一样啊
  • tuyer

    @wowo: 现在的高通平台是生成多个DTB文件, 然后再用一个dtbtool将其整合成一个dt.img文件放在 boot.img中, 我想问一下有没有办法 将dt.img反汇编成一个dtb文件 或将多个dtb文件合成一个有效的dtb文件? 谢谢
    • wowo

      @tuyer:这里有dtbtool的源码,你可以看一下,写一个代码去解它。或者去谷歌搜索一些,应该有人给出了解包的工具。
  • callme_friend

    看了关于devicetree的博文后,有些疑问想请教: 1)传递进来的dtb被内核解析为device_node链表;那内核又是在哪里把他们转变成device呢? 2)而且按照由根到叶的启动顺序,必须是先有总线,然后才能在总线上注册设备,那dts有反应总线的吗?对于arm中常用的platform,在dts文件中没有看到platform的关键字。
    • linuxer

      @callme_friend:1)在machine初始化那一小节中有描述的(of_platform_populate),你再仔细看看。 2)对于其他平台我不是很熟悉,我这里只是描述ARM平台的内容。对于ARM,device tree描述的device node被加入platform bus或者是amba-bus。
      • callme_friend

        @linuxer:也就是说,arm dts中的设备,除了cpu、memory、chosen、interrupt节点,全部都默认挂入platform总线?
        • wowo

          @callme_friend:是的,DTS只描述platform device(AMBA除外)。这是基于这样的考虑:其它bus上的device,其bus应该具备动态枚举设备的能力。只有platform device,才一开机就存在,需要靠device tree来描述。
          • jinxin 主页

            @wowo:请问那像一些i2c, spi设备呢?也是会挂在platform bus下么? 这好象不合适把
            • wowo

              @jinxin:这些设备当然是挂在i2c或者spi bus下,然后枚举是由i2c bus的driver负责。
  • rabbiteve

    看了三遍,每看一次都有新的收获。我现在碰到到dts解析出错: [ 0.000000] Invalid tag cc233e80 in flat device tree! [ 0.000000] Weird tag at end of node: cc233e80 [ 0.000000] Weird tag at end of node: cc233e80 [ 0.000000] Weird tag at end of node: cc233e80 [ 0.000000] Weird tag at end of node: cc233e80 [ 0.000000] Weird tag at end of node: cc233e80 [ 0.000000] Weird tag at end of node: cc233e80 [ 0.000000] Weird tag at end of tree: cc233e80 可是编译和反编译dts文件都没有问题。这种情况可能会是什么原因?或者应该怎么去查这种问题? 有时删掉一些无关的注释,还 会导致内核起不来。 谢谢!
    • wowo

      @rabbiteve:建议检查一下传递DTB的内存,大小是否足够,是否有异常,是否被其它软件覆盖。
      • rabbiteve

        @wowo:非常感谢, 我跟下内核最开始的启动代码,看看是否DTB区域被其他软件覆盖。目前我是将DTB放在zImage后面,再打uImage的头部。
      • rabbiteve

        @wowo:正如你说的,确实是DTB被BSS段覆盖了。 cat arch/arm/boot/zImage arch/arm/boot/dts/dra7-evm.dtb > zImage.dtb ./mkimage -A arm -O linux -C none -T kernel -a 0x80008000 -e 0x80008000 -n 'linux.dra7-evm.ByCxh' -d zImage.dtb uImage.dra7-evm.dtb 然而arch/arm/boot/compressed/: 58 _edata = .; 59 60 . = ALIGN(8); 61 __bss_start = .; 我在__bss_start前面预留段空间给DTB,就正常了: 61 . += 0x20000; /* reserved for dtb */ 62 __bss_start = .; 非常感谢!
  • wowo

    @tastier 一些参考标签在反编译的DTS文件中显示为数字,这个没有影响吗? ============ 没有影响,数字是那些名字的一个句柄而已。
  • callme_friend

    博主,关于device_node结构中的以下几个成员: sibling next allnext 请问他们之间有什么区别? struct device_node *sibling; struct device_node *next; /* next device of same type */ struct device_node *allnext; /* next in list of all nodes */ 有没有重复的意思?
    • linuxer

      @callme_friend:device tree node是树状结构,属于一个parent的device node通过sibling形成一个单向链表。 本来next是链接同一个device type的node。例如同属于cpu type或者memory type的node会通过next进行链接,在遍历属于同一种device type的node的时候需要。 除了child<--->parent这样的树状的拓扑结构,所有的device还链接成一个单向链表of_allnodes,allnext就是链接入那个链表的节点。 next和allnext的确是有点重复,用child<--->parent的结构来保存系统中的所有的device node的拓扑就足够了,因此,新的内核已经删除了next和allnext。
      • callme_friend

        @linuxer:cpu0和cpu1都属于cpu下的子节点;且又是同一类型。 那cpu0和cpu1之间,岂不是既有next也有sibling连接?
        • linuxer

          @callme_friend:是的,不过新的内核中只有sibling连接了
          • callme_friend

            @linuxer:suoga! 谢谢
  • marx

    请教下,arm linux 在启动过程中有没什么针对设备树的debug手段? 我记得以前在玩 powerpc 时,内核在启动时会有一个开关可以控制是否将加载的设备树打印出来;想问下, 在arm 下面,有无类似的调试开关?
    • wowo

      @marx:不是启动过程是否可以?例如/proc/device-tree/?
  • lutianqian

    看了楼主的三篇文章,对设备树有更深刻的理解。不过本人还在学习中。还望楼主有空的时候能不能讲一些,设备树最终演化为平台设备端的时候,它的那些属性是plat_device下的res或者dev.platform_data,他们对应关系如何的呢?
    • wowo

      @lutianqian:可以参考如下的过程: of_platform_bus_create-->of_platform_device_create_pdata-->of_device_alloc-->of_address_to_resource 本文有提到,你可以结合文档和代码,自己理解一下。
  • 南山南

    能否请教一个问题: 在dts文件中指定了中断: qcom,mdss_mdp@1a00000{ interrupts = <0x0 0x48 0x0> ..... } 在代码中使用res = platform_get_resource(ofdev,IORESORCE_IRQ,0). 得到的res->start和dts中的不一样(0x48)。 中间有什么转换步骤么? 感谢
    • wowo

      @南山南:这要看你使用的平台的IRQ domain是否提供了translation机制,具体可参考irq_create_of_mapping(kernel/irq/irqdomain.c): /* If domain has no translation, then we assume interrupt line */ if (domain->ops->xlate == NULL) hwirq = intspec[0]; else { if (domain->ops->xlate(domain, controller, intspec, intsize, &hwirq, &type)) return 0; } 有关“domain->ops->xlate”,可参考“/irq_subsystem/irq-domain.html”。
  • 爱萝莉

    楼主您好: 文章中所说的 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。 有些问题不明白 1.root节点下面的address-cell size-cell 是32位的整形数,dt_root_addr_cells + dt_root_size_cells =? 这里的和食指32位整形数最大值之和?
    • linuxer

      @爱萝莉:reg属性的内容是一个数组,如下: 地址A, size A 地址B, size B 地址C, size C ...... 因此,即系reg属性最重要的是要知道(地址X +size X)的数据长度,这个长度是由dt_root_addr_cells + dt_root_size_cells定义的。
  • 内核初学者

    看了楼主的博客知识增长了许多,首先表示感谢,同时请教一个问题,我一直困扰着:设备树文件传给内核,内核要读取dtob文件,然后解析,解析的第一步是匹配平台的compatible,也就是说只有内核里有的平台才能支持,如果这一款CPU平台linux内核没有是不支持的吗?还有就是如何匹配呢?内核支持的平台列表在哪里呢?是如何生成列表的呢?找了一天都没找到,求楼主支持答疑,谢谢!
    • linuxer

      @内核初学者:也就是说只有内核里有的平台才能支持,如果这一款CPU平台linux内核没有是不支持的吗? ------------------------- 目前内核支持了很多的硬件平台,如果你工作的硬件平台内核不支持的话,那么需要自己撰写相关的代码来支持 还有就是如何匹配呢?内核支持的平台列表在哪里呢?是如何生成列表的呢? ------------------------ 在setup_machine_fdt--->of_flat_dt_match_machine函数中进行匹配。 of_flat_dt_match_machine是在machine描述符的列表中scan,找到最合适的那个machine描述符。我们首先看如何组成machine描述符的列表。和传统的方法类似,也是静态定义的。DT_MACHINE_START和MACHINE_END用来定义一个machine描述符。编译的时候,compiler会把这些machine descriptor放到一个特殊的段中(.arch.info.init),形成machine描述符的列表
  • xinyuandexin

    请问一下,我如果自己设计的板子,要做设备树添加,内核里是不是要静态写如下类似的代码呢? #ifdef CONFIG_SOC_AM33XX static const char *const am33xx_boards_compat[] __initconst = { "ti,am33xx", NULL, }; DT_MACHINE_START(AM33XX_DT, "Generic AM33XX (Flattened Device Tree)") .reserve = omap_reserve, .map_io = am33xx_map_io, .init_early = am33xx_init_early, .init_machine = omap_generic_init, .init_late = am33xx_init_late, .init_time = omap3_gptimer_timer_init, .dt_compat = am33xx_boards_compat, .restart = am33xx_restart, MACHINE_END #endif
    • linuxer

      @xinyuandexin:增加这部分内容是必须的
  • 爱萝莉

    楼主你好: 我有一个疑惑在函数of_platform_bus_create( )->for_each_child_of_node() 一个device node可能是一个桥设备,因此要重复调用of_platform_bus_create来把所有的device node处理掉 我的理解是:比如SOC/I2C i2c下面还挂着N个设备,for_each_child_of_node() 一个一个创建这几个设备节点是吗?
    • wowo

      @爱萝莉:of_platform_bus_create只负责创建platform bus下的设备(正如其名),其它bus下的设备(如I2C、SPI等),则由对应bus的driver core(如I2C core)负责创建。
      • 爱萝莉

        @wowo:嗯嗯,打印log看了下 of_platform_bus_create创建的设备全是/soc下面的节点。其他如chosen节点没有子节点就不会调用了。^_^
  • 爱萝莉

    楼主你好: Documentation/arm/Booting.txt中 The device tree blob (dtb) must be no bigger than 2 megabytes in size and placed at a 2-megabyte boundary within the first 512 megabytes from the start of the kernel image android/kernel/Documentation/zh_CN/arm/Booting bootloader 必须传递一个系统内存的位置和最小值,以及根文件系统位置。dtb 必须置于内核自解压不会覆盖的内存区。建议将其放置于 RAM 的头 16KiB中。 可不可以这样理解,bootloader把dtb加载到RAM的前512M字节区域,但是又不能在Zimage解压的范围内?
    • 郭健

      @爱萝莉:根据上面的英文描述,对dtb的要求如下: 1、不能大于2MB 2、首地址必须要对齐在2MB上 3、如果kernel的首地址是A,那么dtb必须位于(A,A+512MB) 的地址范围内。
    • 爱萝莉

      @爱萝莉:hello 楼主:还有一个问题不明白 /kernel/arch/arm64/head.S /* * Map the FDT blob (maximum 2MB; must be within 512MB of * PHYS_OFFSET). */ mov x3, x21 // FDT phys address and x3, x3, #~((1 << 21) - 1) // 2MB aligned mov x6, #PAGE_OFFSET sub x5, x3, x24 // subtract PHYS_OFFSET tst x5, #~((1 << 29) - 1) // within 512MB? csel x21, xzr, x21, ne // zero the FDT pointer b.ne 1f add x5, x5, x6 // __va(FDT blob) add x6, x5, #1 << 21 // 2MB for the FDT blob 这里就判断内存分配给FDT的内存是否大于2M吧,然后我看了dt.img 大概有9M左右。这个怎么解释呢。?
  • 摩斯电码

    你好,我发现在函数of_platform_bus_create中并不一定对所有的device node都进行遍历,比如像下面一个设备树: i2c-gpio-0 { compatible = "i2c-gpio"; gpios = <0x3e 0x2 0x0 0x3e 0x3 0x0>; i2c-gpio,delay-us = <0x2>; #address-cells = <0x1>; #size-cells = <0x0>; status = "okay"; mma7660@4c { compatible = "freescale,mma7660"; reg = <0x4c>; interrupt-parent = <0x3f>; interrupts = <0x1 0x2>; poll_interval = <0x64>; input_fuzz = <0x4>; input_flat = <0x4>; status = "okay"; }; }; 其中i2c-gpio-0这个device node确实是在of_platform_bus_create的时候被注册为了platform_device,但是mma7660@4c这个设备节点并没有被of_platform_bus_create遍历到,它是在注册i2c_adapter的时候,会遍历这个adapter对应的device node的child device node(目的是注册i2c_client),此时才会处理mma7660这个device node。 不知道你有没有知道注意到在一般在调用of_platform_bus_create的时候会传递一个参数: of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL) 其中of_default_bus_match_table的定义如下: const struct of_device_id of_default_bus_match_table[] = { { .compatible = "simple-bus", }, { .compatible = "simple-mfd", }, #ifdef config_arm_amba { .compatible = "arm,amba-bus", }, #endif /* config_arm_amba */ {} /* empty terminated list */ }; 意思是如果某个device node的compatible属性跟上面数组中定义的match,那么这个device node的child device node才会被注册为platform_device。 在函数of_platform_bus_create中有下面几句: dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); if (!dev || !of_match_node(matches, bus)) return 0; for_each_child_of_node(bus, child) { pr_debug(" create child: %s\n", child->full_name); rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); if (rc) { of_node_put(child); break; } } 其中如果of_platform_device_create_pdata成功执行,然后会调动of_match_node,如果match的话,在会再次递归这个device node的下的child device node,如果不match,就直接返回了,不再遍历其下的child device node了。
    • 云

      @摩斯电码:私以为再去遍历i2c下的child device node没什么意义。因为mma7660着个设备属于i2c bus上的设备,没必要再放到platform_bus上了。
    • 郭健

      @摩斯电码:多谢您的提醒,的确是如此。对于支持热拔插的总线(例如USB),其实不需要体现在dts中,其对应的device数据结构(例如struct usb_device)会在设备插入的时候自行分配。而那些不能支持热拔插的总线,挂入其上的device需要在dts中体现并在总线设备初始化的时候进行实例化(就是分配一个xxx_device的数据结构并注册到设备模型)。在所有不支持hotplug的总线中,platform是一个特殊的总线,没有实际的bus设备对应,因此在machine init中调用of_platform_populate处理。
  • imjacob

    你好,请教下, 我的板子如果是sd卡或者emmc这种,那怎么用device tree来分区呢?我网上查了很久,只有查到对nand,nor这些raw的存储设备分区的方法(就是用partition)。但sd之类找不到,谢谢!
    • wowo

      @imjacob:在kernel driver中,只有MTD driver才会关注分区的概念,在新时代(eMMC)没有关注的必要。NAND和eMMC的简单区别如下: 奇葩文件系统(yaffs等)----------->MTD---->NAND/NOR controller---->NAND/NOR 普通文件系统(FAT、EXT4等)---->MMC---->eMMC controller---------->NAND 分区(partition)是一个软件概念,理应由文件系统负责抽象和处理,那么问题来了,为什么MTD driver(注意,是driver哦)需要关注分区的概念?这是现存Linux系统中MTD、NAND等有关实现的一个不友好的地方。 然后,eMMC出现了,NAND有关坏块/均衡有关的实现,统一做到eMMC片子里面了,Linux kernel以及之上的文件系统,都happy了,就没有必要在kernel中关注分区这个事情了。
      • imjacob

        @wowo:恩。很感谢大家的回复,收益良多。我又理解了一下,mtd的分区和 sd(及emmc,磁盘)的分区是不一样的,前者 特定于linux的实现,用了什么mtd_partition等结构来维护,而后者是有标准的,什么MBR上面的4个主分区这些概念。是这样吗?
    • 郭健

      @imjacob:如果你的板子是SD或者eMMC的话,目前条件下,内核是不支持通过device tree来传递分区信息的,为什么呢?因为协议已经支持分区了,如果driver在面对一个SD或者eMMC设备的时候,能够通过命令从硬件获取分区信息,基本我认为也就不需要参数传递分区信息了。
      • 郭健

        @郭健:你也可以考虑把eMMC当成一个普通的磁盘,这样,在磁盘上建立分区表(例如MBR或者GPT什么的),也就不需要device tree来传递参数了。
        • imjacob

          @郭健:@郭健 @wowo, 恩。很感谢大家的回复,收益良多。我又理解了一下,mtd的分区和 sd(及emmc,磁盘)的分区是不一样的,前者 特定于linux的实现,用了什么mtd_partition等结构来维护,而后者是有标准的,什么MBR上面的4个主分区这些概念。是这样吗?
          • wowo

            @imjacob:差不多是这个意思,不过“什么MBR上面的4个主分区这些概念”不太准确,这4个主分区,是物理上的分区概念(出厂就确定了),而我们平时所示的分区,大多是软件层面(FS的角度)的逻辑概念。
            • imjacob

              @wowo:恩。谢谢! 我查了一下,为什么现在的硬盘,sd卡,u盘只能分为4个主分区。这个可能和历史有关。历史上,当然,硬盘的容量不大。所以保留了第一个扇区为引导扇区,就是MBR。作为引导,其实还不止引导,还放入了一些其他信息,例如分区的信息。当初留给的是64个字节。而每个主分区呢,要16个字节,所以只能4个主分区了。 这个虽然现在硬盘什么大的吓死人了,但是还是保留了下来。 再次感谢!
              • wowo

                @imjacob:确实如此,也多谢你的分享~~~
  • snake

    请教一个问题, 文中说: "我们知道,对于s3c2416的interrupt controller而言,其#interrupt-cells的属性值是4,定义为。每个域的解释如下" 请问每个域的解释如何得知的? 是从文档还是代码里面? 能说的稍微详细些吗,谢谢!
    • wowo

      @snake:DTS的东西,一般都放在Documentation/devicetree/bindings/目录中了,例如: Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt
      • snake

        @wowo:@wowo: 谢谢, s3c24xx的明白了, 那比如mtk,qualcom平台的#interrupt-cells的各个属性含义怎么看呢? 比如mtk的gic, #interrupt-cells = <3>; 这3个属性含义如何看呢? Documentation/devicetree/bindings 下面好像没有说明吧, 麻烦请指教下, 谢谢!
        • wowo

          @snake:如果没有说明,就去看一下source code吧。 在linux kernel的世界里,source code是万能魔法棒,什么问题都能回答:-)
          • snake

            @wowo:好的,谢谢 ^_^
  • js_wawayu

    请教一下boot cpu是不是可以有多个?
    • wowo

      @js_wawayu:应该不能吧?
  • simonzhang

    感谢郭同学获益良多
    • linuxer

      @simonzhang:太客气了,Simon同学,有空也多多贡献,多多分享啊,要不你脑袋里面的知识要发霉了,哈哈
  • borman

    楼主,有一事不明,我在of_platform_bus_create中并没有找到与创建platform device时设备的操作方法的初始化,还是这里只是对设备节点的初始化,并没有设备的具体操作?
    • wowo

      @borman:不太明白你的意思,这个接口负责根据DTS,创建对应的device,并将其添加到kernel中: of_platform_bus_create of_platform_device_create_pdata of_device_add device_add device被添加进kernel之后,就按照设备模型,进行正常的probe操作。
      • borman

        @wowo:搞混了,那么向系统中添加了这些dts中设备,它们的驱动是何时注册呢,我是想说这个的
        • linuxer

          @borman:在各自驱动模块的初始化函数中注册驱动啊
  • 大头元

    楼主你好: 很多驱动程序的probe函数中都会先去调用dev_get_platdata(&pdev->dev)获取platform_data,我想知道在设备树的条件下,platform_data是怎么来的啊?它跟of_device_id中的.data有关吗,但我发现.data并没赋值(如果有关的话)。
    • wowo

      @大头元:没有关系。 platform_data是driver调用dev_set_platdata自行设置的。 你可以去kenerl里面看看例子,看看别人怎么用的。
      • 大头元

        @wowo:多谢楼主,我用的是3.14内核,没找到dev_set_platdata定义。而且我看的栗子直接就在probe函数中使用了dev_get_platdata,probe是匹配后第一个调用的函数,估计是在某个地方赋值好了吧。platform_data是提前在内核中写好的还是用设备树转化而来的啊?
        • wowo

          @大头元:抱歉,我看成了dev_set_drvdata可。 这个指针,要么直接赋值: pdev->dev.platform_data = pdata; 要么调用platform_device_add_data,和device tree没有关系。
          • 大头元

            @wowo:ok,多谢答疑解惑~
  • jey

    Hi,wowo: 现在项目中遇到一个问题,下面是相关的log: _kernel_ring_buffer [ 0.000000] Initializing cgroup subsys cpu [ 0.000000] Initializing cgroup subsys cpuacct [ 0.000000] Linux version 3.10.73-ge5fb304-dirty (caojianye@compiler191) (gcc [ 0.000000] CPU: AArch64 Processor [410fd034] revision 4 [ 0.000000] [ 0.000000] Error: invalid device tree blob at physical address 0x00000000122 [ 0.000000] The dtb must be 8-byte aligned and passed in the first 512MB of m [ 0.000000] [ 0.000000] Please check your bootloader. 现象是在内核中增加两行打印后内核就不能启动,从log分析是dtb被踩,lk中能正常解析dtb,说明是在内核出事话的时候踩到了dtb,请教一下: 1.内核初始化的哪个阶段可能踩到dtb呢? 2.我的想法是在lk中把dtb的加载地址往后面挪一些,但是如果内核在进一步增大的话还是会有问题,并不能彻底解决,能不能通过修改内核代码彻底解决这种问题呢?
    • wowo

      @jey:bootloader会通过命令行参数告诉kernel,当前可用的memory空间是多少。只要这个空间和dtb所占的空间不重叠,就不会有问题。
  • 麦兜要努力

    楼主您好: 刚开始接触Linux driver,目前在学习dw dma的部分。无意中发现了楼主的文章,看了关于device tree和platform相关的部分,解决了一些platform_device和platorm_driver的疑惑,不过还是有些问题卡住了,想请教一下: 在引入device tree之前要静态定义platform_device struct,注册device,然后定义platform_driver struct,注册driver,进行match,执行probe。引入device tree之后,最终通过of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)系统生成platform_device并注册到系统中。就我目前看的dw dma部分,一开始subsys_initcall(dw_init)->dw_init->platform_driver_register(&dw_driver),看到: static struct platform_driver dw_driver = { .probe = dw_probe, .remove = dw_remove, .shutdown = dw_shutdown, .driver = { .name = DRV_NAME, .pm = &dw_dev_pm_ops, .of_match_table = of_match_ptr(dw_dma_of_id_table), .acpi_match_table = ACPI_PTR(dw_dma_acpi_id_table), }, }; #define DRV_NAME "dw_dmac" #ifdef CONFIG_OF static const struct of_device_id dw_dma_of_id_table[] = { { .compatible = "snps,dma-spear1340" }, {} }; MODULE_DEVICE_TABLE(of, dw_dma_of_id_table); #endif 根据这个snps,dma-spear1340,找到spear13xx.dtsi文件。 可以在dts里面看到: ahb { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; ranges = <0x50000000 0x50000000 0x10000000 0x80000000 0x80000000 0x20000000 0xb0000000 0xb0000000 0x22000000 0xd8000000 0xd8000000 0x01000000 0xe0000000 0xe0000000 0x10000000>; sdhci@b3000000 { compatible = "st,sdhci-spear"; reg = <0xb3000000 0x100>; interrupts = <0 28 0x4>; status = "disabled"; }; cf@b2800000 { compatible = "arasan,cf-spear1340"; reg = <0xb2800000 0x1000>; interrupts = <0 29 0x4>; status = "disabled"; dmas = <&dwdma0 0 0 0 0>; dma-names = "data"; }; dwdma0: dma@ea800000 { compatible = "snps,dma-spear1340"; reg = <0xea800000 0x1000>; interrupts = <0 19 0x4>; status = "disabled"; dma-channels = <8>; #dma-cells = <3>; dma-requests = <32>; chan_allocation_order = <1>; chan_priority = <1>; block_size = <0xfff>; dma-masters = <2>; data_width = <3 3 0 0>; }; dma@eb000000 { compatible = "snps,dma-spear1340"; reg = <0xeb000000 0x1000>; interrupts = <0 59 0x4>; status = "disabled"; dma-requests = <32>; dma-channels = <8>; dma-masters = <2>; #dma-cells = <3>; chan_allocation_order = <1>; chan_priority = <1>; block_size = <0xfff>; data_width = <3 3 0 0>; }; ..... 疑问: 在spear1340c.里面看到: static void __init spear1340_dt_init(void) { of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); platform_device_register_simple("spear-cpufreq", -1, NULL, 0); } static const char * const spear1340_dt_board_compat[] = { "st,spear1340", "st,spear1340-evb", NULL, }; DT_MACHINE_START(SPEAR1340_DT, "ST SPEAr1340 SoC with Flattened Device Tree") .smp = smp_ops(spear13xx_smp_ops), .map_io = spear13xx_map_io, .init_time = spear13xx_timer_init, .init_machine = spear1340_dt_init, .restart = spear_restart, .dt_compat = spear1340_dt_board_compat, MACHINE_END 那这边有调用of_platform_populate,那dma node是在platform bus下面的,应该是有被创建了platform_device了吧?但是我根据 .name = "dw_dmac",我又找到了关于dw_dmac0_device的静态定义: static struct dw_dma_platform_data dw_dmac0_data = { .nr_channels = 3, .block_size = 4095U, .nr_masters = 2, .data_width = { 2, 2, 0, 0 }, }; static struct resource dw_dmac0_resource[] = { PBMEM(0xff200000), IRQ(2), }; DEFINE_DEV_DATA(dw_dmac, 0); DEV_CLK(hclk, dw_dmac0, hsb, 10); #define DEFINE_DEV_DATA(_name, _id) \ static u64 _name##_id##_dma_mask = DMA_BIT_MASK(32); \ static struct platform_device _name##_id##_device = { \ .name = #_name, \ .id = _id, \ .dev = { \ .dma_mask = &_name##_id##_dma_mask, \ .platform_data = &_name##_id##_data, \ .coherent_dma_mask = DMA_BIT_MASK(32), \ }, \ .resource = _name##_id##_resource, \ .num_resources = ARRAY_SIZE(_name##_id##_resource), \ } 以及dw_dmac0_device注册: static int __init system_device_init(void) { platform_device_register(&at32_pm0_device); platform_device_register(&at32_intc0_device); platform_device_register(&at32ap700x_rtc0_device); platform_device_register(&at32_wdt0_device); platform_device_register(&at32_eic0_device); platform_device_register(&smc0_device); platform_device_register(&pdc_device); platform_device_register(&dw_dmac0_device); platform_device_register(&at32_tcb0_device); platform_device_register(&at32_tcb1_device); platform_device_register(&pio0_device); platform_device_register(&pio1_device); platform_device_register(&pio2_device); platform_device_register(&pio3_device); platform_device_register(&pio4_device); return 0; } 那究竟这个platform_device,究竟是在of_platform_populate里面去创建和注册的,通过snps,dma-spear1340去匹配?还是说在platform_device_register(&dw_dmac0_device)这边静态定义并被注册,通过name dw_dmac去匹配?为什么会既存在于dts里面,又静态去定义呢,会不会冲突呢?而且有两个同样compatible string的dma,只是address不同,那应该是根据设备号id去区分,在静态定义的时候可以设置为0和1,但是在of_platform_populate里面的of_device_add,初始值是-1,那有两个同样的设备,这个id它会做什么样的变化呢? 希望能得到楼主的解答,感谢!
    • wowo

      @麦兜要努力:我觉得,代码是为了兼容OF enable(DTS场景)和disable(传统的mach场景)两种使用方式,不会共存。 你可以按照这个思路,查找一下代码,找到支持或者驳斥这个观点的证据(重点关注CONFIG_OF等和device tree有关的配置项)。
  • lucky

    wowo你好: 请教一个问题: /* 扫描DTB中的memory node,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息。*/ of_scan_flat_dt(early_init_dt_scan_memory, NULL); early_init_dt_scan_memory early_init_dt_add_memory_arch arm_add_memory 初始化meminfo.bank[meminfo.nr_banks]; 这个是扫描DTS里面定义的系统所有的物理内存信息吗?
    • wowo

      @lucky:总的来说,是的。不过更细节一些内容,Linuxer同学正在写内存初始化有关的内容,随后应该有一篇基于x project的实战性的文档,应该可以回答你的问题。
    • linuxer

      @lucky:可以参考/memory_management/mm-init-1.html这份文档,这里讲了内核如何形成物理内存的布局信息。 BTW,现在应该已经不使用meminfo来保存内存的信息,在初始化阶段,都使用memblock来保存物理内存信息。
      • lucky

        @linuxer:多谢linuxer,接下来细细品味,^_^
  • helloworld

    请教wowo,编译的内核中有很多IRQCHIP_DECLARE定义的of_device_id结构体,对于interrupt controller的匹配,of_irq_init会去遍历所有的of_device_id结构体吗? 这里的匹配是根据dts的interrupt-controller节点和of_device_id的compatible属性进行匹配的吗?我看了很久好像没有看到,能指点下具体在哪里吗?
    • helloworld

      @helloworld:是我太粗心了,已经找到了,原来是在__of_match_node对matches轮询匹配root interrupt controller.
  • yellow

    @wowo: 请教下,我打log发现,扫描device tree添加device的时候,并不是所有node都会注册成设备的,除了soc,已经下一层的soc/***/,就是说只会注册device tree根节点已经第二层节点,请问对吗,代码从哪里体现出来呢? <3>[ 2.032465] [4: swapper/0: 1] create bus: /soc <3>[ 2.038388] [4: swapper/0: 1] create child: /soc/sec_hw_param <3>[ 2.043990] [4: swapper/0: 1] create child: /soc/qcom,smp2p-modem@17911008 <3>[ 2.052261] [4: swapper/0: 1] create child: /soc/qcom,smp2p-adsp@17911008 .....
    • wowo

      @yellow:是的,你可以往下翻翻之前的留言,其中有同学讨论过这个事情: /device_model/dt-code-analysis.html (搜索simple-bus这个关键字)
  • yellow

    @wowo: 我结合代码重新看了这篇文章,有个地方想不明白: phandle 这个属性值,根据什么初始化产生的呢?(我的理解是每个节点都有这个属性,因为其他节点需要引用) 比如 interrupt-map = <0 0 0 0 &intc 0 0 405 0> ,这个&intc,从代码看是根据它的phandle,来找到intc所在的节点 但从dtsi上,intc这个节点没看到有定义 “phandle”"linux,phandle"的属性。那么intc这个节点的phandle从哪里产生的? 劳烦解答一下,谢谢。
    • wowo

      @yellow:phandle是编译器(dtc)自行添加的,具体你可以去扒扒dtc的source code。
      • yellow

        @wowo:谢谢。。看您的文章越读越觉得受益匪浅~
  • 小明

    wowo大神你好,看你的文章受益匪浅,真的是用嵌入式工程师的思维讲解,很容易理解接受。 最近遇到个问题,我想在用户空间获取并解析整个设备树,来获取一些配置信息。但用户空间改怎么获取呢?有现成的方案吗?谢谢
    • maze

      @小明:这个应该是有的/proc下面是有一个device tree的东西吧。不过印象是通过defconfig配置出来的选项
    • mikhail

      @小明:kernel_path/drivers/of/Kconfig config PROC_DEVICETREE 重新编译内核,系统就会生成文件 /proc/device-tree
  • 勾越

    1、您好,我没在内核中找到全局变量struct device_node *of_allnodes(就是指向设备树的global list)啊?system.map也找了,也还是没有。
  • 勾越

    1、您好,文中的:全局变量struct device_node *of_allnodes就是指向设备树的global list 我在内核里面没有找到啊???我在system.map里面也找了。
    • hello

      @勾越:文章中有这样一句话 注:本文中的linux kernel使用的是3.14版本。 可能不同的内核版本会有不同
      • 勾越

        @hello:您好,我的是kernel4.1版本,请问有什么方法去找到我这个global list的全局指针变量呢?
        • jake

          @勾越:你找找&of_root
  • Warren

    补充一点:i2c和spi有点特殊,i2c和spi有主从设备之分,并且有自己的总线i2c bus和spi bus。挂载在platform bus下的只是i2c和spi的controller设备(即master),以iMX的i2c举例,在执行of_platform_populate()时创建的platform device只代表了I2C controller,在注册platform_driver(如i2c_imx_driver)时会执行platform_driver的probe函数,即i2c_imx_probe(),probe函数会分配一个i2c_adapter作为i2c controller的抽象设备挂接在i2c bus上。同时该probe函数会遍历该i2c controller设备树节点下的所有i2c子节点,创建i2c_client设备并将i2c_client->adapter指向这个i2c_adapter。随后在i2c client加入i2c bus时或者i2c client的驱动i2c_driver加入i2c bus时,会匹配i2c_client与i2c_driver,匹配成功会执行i2c_driver的probe函数来初始化i2c_client。i2c_client通过adapter指针指向自己的i2c_adapter,i2c_adapter实现i2c协议传输的方法。上层应用在使用i2c_client的字符设备时最终通过该指针来操作i2c_adapter发出正确的时序和数据。
    • Warren

      @Warren:回错地方了,本来是想回复这个兄弟的,无奈回复中出现了敏感词(sla-ve),改了敏感词后被重置到首页了: jinxin 2015-12-22 10:49 @wowo:请问那像一些i2c, spi设备呢?也是会挂在platform bus下么? 这好象不合适把
  • 一一

    hi,wowo大神,想请教一下 cpus node的中对compatible = "arm,cortex-a7"是如何处理的 我大致追了一下源码: rest_init-->kernel_init-->smp_prepare_cpus-->init_cpu_topology-->parse_dt_topology中有一段 for (cpu_eff = table_efficiency; cpu_eff->compatible; cpu_eff++) if (of_device_is_compatible(cn, cpu_eff->compatible)) break; static const struct cpu_efficiency table_efficiency[] = { {"arm,cortex-a15", 3891}, {"arm,cortex-a7", 2048}, {NULL, }, }; 但是table_efficiency只有a15和a7 没看见a9等其他型号,所以不知道我找的是不是正确,以上是32位arm。 64位arm分析半天,好像都没有看见比较compatible属性。
  • 在渊

    wowo大佬,请教一下。设备总线驱动模型中,在总线创建时会注册设备,如文中platform总线从device_node遍历后会有device_add()动作,那为啥在驱动中,我们往往也还有注册class,注册device的动作。驱动中的device往往用于和用户态交互,这里是否就是为了这个原因创建,这里的设备如何和设备模型统一起来。
  • aha

    从arm64的setup_arch来看,arm64架构不需要做platform匹配,只检查了root节点是否有model或者compatible。也就是说不同的arm64的soc都是无差异的setup arch过程,不需要实现struct machine_desc结构体中各种回调。这是因为arm64的soc真的不需要,还是这种差别化分散到其他驱动?