zynq linux 3.15 ARM Device Tree 的初始化

arch\arm\mach-zynq\common.c完成设备树初始化

DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
	.smp		= smp_ops(zynq_smp_ops),
	.map_io		= zynq_map_io,
	.init_irq	= zynq_irq_init,
	.init_machine	= zynq_init_machine,
	.init_late	= zynq_init_late,
	.init_time	= zynq_timer_init,
	.dt_compat	= zynq_dt_match,
	.reserve	= zynq_memory_init,
	.restart	= zynq_system_reset,
MACHINE_END
zynq_init_machine函数中的of_platform_populate函数执行设备树初始化代码
/**
 * zynq_init_machine - System specific initialization, intended to be
 *		       called from board specific initialization.
 */
static void __init zynq_init_machine(void)
{
	struct platform_device_info devinfo = { .name = "cpufreq-cpu0", };

	of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);

	platform_device_register(&zynq_cpuidle_device);
	platform_device_register_full(&devinfo);

	zynq_slcr_init();
}
of_platform_populate 位于 drivers/of/platform.c,是 OF 的标准函数。

这个函数遍历设备树,根据节点创建设备。Populate platform_devices from device tree data

int of_platform_populate(struct device_node *root,
			const struct of_device_id *matches,
			const struct of_dev_auxdata *lookup,
			struct device *parent)
{
	struct device_node *child;
	int rc = 0;

	root = root ? of_node_get(root) : of_find_node_by_path("/");
	if (!root)
		return -EINVAL;

	for_each_child_of_node(root, child) {
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		if (rc)
			break;
	}

	of_node_put(root);
	return rc;
}

在 of_platform_populate 中如果 root 为 NULL,则将 root 赋值为根节点,这个根节点是用 of_find_node_by_path 取到的。

struct device_node *of_find_node_by_path(const char *path)
{
	struct device_node *np = of_allnodes;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	for (; np; np = np->allnext) {
		if (np->full_name && (of_node_cmp(np->full_name, path) == 0)
		    && of_node_get(np))
			break;
	}
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return np;
}

在这个函数中有一个很关键的全局变量:of_allnodes,它的定义是在 drivers/of/base.c 里面。

struct device_node *of_allnodes;
EXPORT_SYMBOL(of_allnodes);

这应该所就是那个所谓的device tree data”了。它应该指向了 device tree 的根节点。问题又来了,这个 of_allnodes 又是咋来的呢?我们知道 device tree 是由 DTC(Device Tree Compiler)编译成二进制文件 DTB(Ddevice Tree Blob),然后在系统上电之后由 bootloader 加载到内存中去,这个时候还没有 device tree,而在内存中只有一个所谓的 DTB,这只是一个以某个内存地址开始的一堆原始的 dt 数据,没有树结构。kernel 的任务需要把这些数据转换成一个树结构然后再把这棵树的根节点的地址赋值给 of_allnodes 就行了。这个过程一定是非常重要,因为没有这个 device tree 那所有的设备就没办法初始化,所以这个 dt 树的形成一定在 kernel 刚刚启动的时候就完成了。


既然如此,我们来看看 kernel 初始化的代码(init/main.c)。


asmlinkage __visible void __init start_kernel(void)
{
............
setup_arch(&command_line);
............
}
这个 setup_arch 就是各个架构自己的设置函数(arch 是 architecture 的缩写),哪个参与了编译就调用哪个,在本文中应当是 arch/arm/kernel/setup.c 中的 setup_arch。

void __init setup_arch(char **cmdline_p)
{
............
mdesc = setup_machine_fdt(__atags_pointer);
...........
   unflatten_device_tree();
    ...............
}

setup_machine_fdt,其中 fdt 的 f 就是扁平flat)的意思。这个时候 DTB 只是加载到内存中的 .dtb 文件而已,这个文件中不仅包含数据结构,还包含了一些文件头等信息,kernel 需要从这些信息中获取到数据结构相关的信息,然后再生成设备树。这个函数的调用还有个参数 __atags_pointer,看名字似乎这是一个指针,干嘛的呢?以后再说,先进入函数看看。

    /* kernel/arch/arm/kernel/devtree.c */
    struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
    {
        ...
    if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
        return NULL;
        ...
    }

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

	/* Setup flat device-tree pointer */
	initial_boot_params = params;
.................
}

phys_to_virt 字面上的意思是物理地址转换成虚拟地址,那就是说 __atags_pointer 是一个物理地址,这就印证了我们的猜测,__atags_pointer 的确是一个指针,再看变量 devtree 它指向了一个 struct boot_param_header 结构体。随后 kernel 把这个指针赋给了全局变量 initial_boot_params。也就是说以后 kernel 会是用这个指针指向的数据去初始化 device tree。


struct boot_param_header {
	__be32	magic;			/* magic word OF_DT_HEADER */
	__be32	totalsize;		/* total size of DT block */
	__be32	off_dt_struct;		/* offset to structure */
	__be32	off_dt_strings;		/* offset to strings */
	__be32	off_mem_rsvmap;		/* offset to memory reserve map */
	__be32	version;		/* format version */
	__be32	last_comp_version;	/* last compatible version */
	/* version 2 fields below */
	__be32	boot_cpuid_phys;	/* Physical CPU id we're booting on */
	/* version 3 fields below */
	__be32	dt_strings_size;	/* size of the DT strings block */
	/* version 17 fields below */
	__be32	dt_struct_size;		/* size of the DT structure block */
};

看这个结构体,很像之前所说的文件头,有魔数、大小、数据结构偏移量、版本等等,kernel 就应该通过这个结构获取数据,并最终生成设备树。现在回到 setup_arch,果然在随后的代码中有这么一个函数。

    /* drivers/of/fdt.c */
    void __init unflatten_device_tree(void)
    {
        __unflatten_device_tree(initial_boot_params, &of_allnodes,
                    early_init_dt_alloc_memory_arch);
        ...
    }


看见了吧,of_allnodes 就是在这里赋值的,device tree 也是在这里建立完成的。__unflatten_device_tree 函数我们就不去深究了,推测其功能应该就是解析数据、申请内存、填充结构等等。


到此为止,device tree 的初始化就算完成了,在以后的启动过程中,kernel 就会依据这个 dt 来初始化各个设备。但是还有一个小问题,那就是在 setup_arch 函数中 __atags_pointer 从何而来。全局搜索这个变量,结构在 arch/arm/kernel/head-common.S 中发现了它的踪迹。


ENDPROC(__mmap_switched)

	.align	2
	.type	__mmap_switched_data, %object
__mmap_switched_data:
	.long	__data_loc			@ r4
	.long	_sdata				@ r5
	.long	__bss_start			@ r6
	.long	_end				@ r7
	.long	processor_id			@ r4
	.long	__machine_arch_type		@ r5
	.long	__atags_pointer			@ r6

     总结,uboot将FDT文件装载到内存,将其物理地址传递给kernel的__atags_pointer变量;

         setup_machine_fdt函数将__atags_pointer转变为虚拟地址,kernel将虚拟地址赋给initial_boot_params结构体变量

     unflatten_device_tree函数访问initial_boot_params结构体变量,将设备树展开,赋给of_allnodes变量。

     Zynq初始化函数zynq_init_machine访问of_allnodes变量,创建具体的总线设备节点。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值