设备树学习(七、dtb到设备节点的转换)

首先我们看一下内核中对一个设备节点的表示


struct device_node {
	const char *name;                    //节点的名字
	const char *type;                    //device_type属性的值
	phandle phandle;                     //对应该节点的phandle属性 
	const char *full_name;               //节点的名字, node-name[@unit-address]从“/”开始的,表示该node的full path 
	struct fwnode_handle fwnode;

	struct	property *properties;        // 节点的属性
	struct	property *deadprops;	/* removed properties 如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表 */
	struct	device_node *parent;         // 节点的父亲
	struct	device_node *child;          // 节点的孩子(子节点)
	struct	device_node *sibling;        // 节点的兄弟(同级节点)
#if defined(CONFIG_OF_KOBJ)              // 在sys文件系统表示
	struct	kobject kobj;        
#endif
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	const char *path_component_name;
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};

从这里我们就可以看到一个设备节点的大概组织形式了。通过下面这三个组织成一个树形结构。

	struct	device_node *parent;         // 节点的父亲
	struct	device_node *child;          // 节点的孩子(子节点)
	struct	device_node *sibling;        // 节点的兄弟(同级节点)

 

device_node结构体中有properties, 用来表示该节点的属性

struct property {
	char	*name;                //属性名字
	int	length;                   //value的长度
	void	*value;               //属性值
	struct property *next;        //指向统一节点的下一个属性
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
	unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
	unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
	struct bin_attribute attr;
#endif
};

 

接下来我以第三节中的例子为例来组织一下设备树在内核中子节点中怎么表示的。

/dts-v1/;
/memreserve/ 0x4ff00000 0x100000;
/ {
    model = "YIC System SMDKV210 based on S5PV210";
    compatible = "yic,smdkv210", "samsung,s5pv210";
 
    #address-cells = <1>;
    #size-cells = <1>;
        
    chosen {
        bootargs = "console=ttySAC2,115200n8 root=/dev/nfs nfsroot=192.168.0.101:/home/run/work/rootfs/";
    };  
 
    memory@30000000 {
        device_type = "memory";
        reg = <0x30000000 0x20000000>;
    };  
};

 

在dtb中是如下表示的

 

下图表示上面的根节点和其里面的两个子节点。

接下来向里面填充属性值

 

首先是把根节点里的4个属性进行填充

在添加上其它两个节点的属性后如下。

 

接着前面几节细化分析:


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

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);    //确认设备树文件,找到最匹配machine_desc 
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
	if (!mdesc) {
		early_print("\nError: invalid dtb and unrecognized/unsupported machine ID\n");
		early_print("  r1=0x%08x, r2=0x%08x\n", __machine_arch_type,
			    __atags_pointer);
		if (__atags_pointer)
			early_print("  r2[]=%*ph\n", 16,
				    phys_to_virt(__atags_pointer));
		dump_machine_table();
	}

	machine_desc = mdesc;
	machine_name = mdesc->name;
	dump_stack_set_arch_desc("%s", mdesc->name);

	if (mdesc->reboot_mode != REBOOT_HARD)
		reboot_mode = mdesc->reboot_mode;

    //标记内核程序在内存的位置
	init_mm.start_code = (unsigned long) _text;
	init_mm.end_code   = (unsigned long) _etext;
	init_mm.end_data   = (unsigned long) _edata;
	init_mm.brk	   = (unsigned long) _end;

	/* populate cmd_line too for later use, preserving boot_command_line */
	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
	*cmdline_p = cmd_line;    //保存命令行参数

	early_fixmap_init();
	early_ioremap_init();

	parse_early_param();      //早期的解析命令行参数

#ifdef CONFIG_MMU
	early_mm_init(mdesc);     //早期的内存管理相关的页表始化
#endif
	setup_dma_zone(mdesc);
	xen_early_init();
	efi_init();
	/*
	 * Make sure the calculation for lowmem/highmem is set appropriately
	 * before reserving/allocating any mmeory
	 */
	adjust_lowmem_bounds();
	arm_memblock_init(mdesc);        //保留内存块,即这些地址保留
	/* Memory may have been removed so recalculate the bounds. */
	adjust_lowmem_bounds();         //更新可用的内存

	early_ioremap_reset();

	paging_init(mdesc);            //建立页表
	request_standard_resources(mdesc);

	if (mdesc->restart)
		arm_pm_restart = mdesc->restart;

	unflatten_device_tree();        //解析转化dtb到属性结构

	arm_dt_init_cpu_maps();
	psci_dt_init();
#ifdef CONFIG_SMP
	if (is_smp()) {
		if (!mdesc->smp_init || !mdesc->smp_init()) {
			if (psci_smp_available())
				smp_set_ops(&psci_smp_ops);
			else if (mdesc->smp)
				smp_set_ops(mdesc->smp);
		}
		smp_init_cpus();
		smp_build_mpidr_hash();
	}
#endif

	if (!is_smp())
		hyp_mode_check();

	reserve_crashkernel();

#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
	handle_arch_irq = mdesc->handle_irq;
#endif

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
	conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
	conswitchp = &dummy_con;
#endif
#endif

	if (mdesc->init_early)
		mdesc->init_early();
}

 

 


void __init arm_memblock_init(const struct machine_desc *mdesc)
{
	/* Register the kernel text, kernel data and initrd with memblock. */
    /* 把内核代码段,数据段,initrd段的物理地址保留,即不允许被覆盖或替换出去 */
	memblock_reserve(__pa(KERNEL_START), KERNEL_END - KERNEL_START);

	arm_initrd_init();            //initrd是内核运行的第一个进程,因为运行时文件系统还没启动,所以这里是内核自带的一个简化的inited进程的初始化

	arm_mm_memblock_reserve();    //保留页表所在范围

	/* reserve any platform specific memblock areas,平台相关的保留 */
	if (mdesc->reserve)
		mdesc->reserve();

	early_init_fdt_reserve_self();        //设备树dtb文件所在内存的地址保留,不能被使用
	early_init_fdt_scan_reserved_mem();   //此函数从早期分配器中获取内存以供设备独占使用

	/* reserve memory for DMA contiguous allocations */
	dma_contiguous_reserve(arm_dma_limit);

	arm_memblock_steal_permitted = false;
	memblock_dump_all();
}

 

 

/**
 * early_init_fdt_reserve_self() - reserve the memory used by the FDT blob
 */
void __init early_init_fdt_reserve_self(void)
{
	if (!initial_boot_params)
		return;

	/* Reserve the dtb region,保留区域的大小 */
	early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
					  fdt_totalsize(initial_boot_params),
					  0);
}

int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base,
					phys_addr_t size, bool nomap)
{
	if (nomap)
		return memblock_remove(base, size);    
	return memblock_reserve(base, size);    
}

//memblock_remove这个函数是把页表中,参数所在物理地址的页表项删除,即不不使用虚拟地址映射这段内存
//memblock_reserve 这个函数是把页表中,参数所在物理地址的页表项标记成保留,即在运行期间不能使用kmalloc之类函数使用

 

 


/**
 * unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens the device-tree passed by the firmware, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 */
void __init unflatten_device_tree(void)
{
    //从initial_boot_params地址开始存放设备树,解析后把设备树挂载of_root节点下面
	__unflatten_device_tree(initial_boot_params, NULL, &of_root,
				early_init_dt_alloc_memory_arch, false);

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

	unittest_unflatten_overlay_base();
}



/**
 * __unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens a device-tree, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 * @blob: The blob to expand
 * @dad: Parent device node
 * @mynodes: The device_node tree created by the call
 * @dt_alloc: An allocator that provides a virtual address to memory
 * for the resulting tree
 * @detached: if true set OF_DETACHED on @mynodes
 *
 * Returns NULL on failure or the memory chunk containing the unflattened
 * device tree on success.
 */
void *__unflatten_device_tree(const void *blob,
			      struct device_node *dad,
			      struct device_node **mynodes,
			      void *(*dt_alloc)(u64 size, u64 align),
			      bool detached)
{
	int size;
	void *mem;

	pr_debug(" -> unflatten_device_tree()\n");

	if (!blob) {
		pr_debug("No device tree pointer\n");
		return NULL;
	}

    //打印dtb头的部分提示信息
	pr_debug("Unflattening device tree:\n");
	pr_debug("magic: %08x\n", fdt_magic(blob));
	pr_debug("size: %08x\n", fdt_totalsize(blob));
	pr_debug("version: %08x\n", fdt_version(blob));

    //检测头结点
	if (fdt_check_header(blob)) {
		pr_err("Invalid device tree blob header\n");
		return NULL;
	}

	/* First pass, scan for size,扫描并计算设备节点+属性总的大小--这个计算很有意思 */
	size = unflatten_dt_nodes(blob, NULL, dad, NULL);
	if (size < 0)
		return NULL;

	size = ALIGN(size, 4);    //4字节对齐
	pr_debug("  size is %d, allocating...\n", size);

	/* Allocate memory for the expanded device tree,对设备树节点结构体,分配内存 */
	mem = dt_alloc(size + 4, __alignof__(struct device_node));
	if (!mem)
		return NULL;

	memset(mem, 0, size);        //清空内存

	*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);    //对最后一个地址存放一个模式,检查溢出

	pr_debug("  unflattening %p...\n", mem);

	/* Second pass, do actual unflattening,建立设备树节点 */
	unflatten_dt_nodes(blob, mem, dad, mynodes);
	if (be32_to_cpup(mem + size) != 0xdeadbeef)    //确认覆盖最高一个4字节空间
		pr_warning("End of tree marker overwritten: %08x\n",
			   be32_to_cpup(mem + size));

	if (detached && mynodes) {
		of_node_set_flag(*mynodes, OF_DETACHED);
		pr_debug("unflattened tree is detached\n");
	}

	pr_debug(" <- unflatten_device_tree()\n");
	return mem;
}


//在上面函数unflatten_dt_nodes函数调用了2次,一次设扫描节点,获取节点个数,一个是扫描并初始化节点

 


/**
 * unflatten_dt_nodes - Alloc and populate a device_node from the flat tree
 * @blob: The parent device tree blob
 * @mem: Memory chunk to use for allocating device nodes and properties
 * @dad: Parent struct device_node
 * @nodepp: The device_node tree created by the call,跟节点
 *
 * It returns the size of unflattened device tree or error code
 */
static int unflatten_dt_nodes(const void *blob,
			      void *mem,
			      struct device_node *dad,
			      struct device_node **nodepp)
{
	struct device_node *root;
	int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH	64
	struct device_node *nps[FDT_MAX_DEPTH];    //设备树最大深度64
    //注意这里传的mem为NULL时,dryrun 为1,表示干运行,不初始化,不建立父子关系,只做统计
	void *base = mem;
	bool dryrun = !base;

	if (nodepp)
		*nodepp = NULL;

	/*
	 * We're unflattening device sub-tree if @dad is valid. There are
	 * possibly multiple nodes in the first level of depth. We need
	 * set @depth to 1 to make fdt_next_node() happy as it bails
	 * immediately when negative @depth is found. Otherwise, the device
	 * nodes except the first one won't be unflattened successfully.
	 */
	if (dad)
		depth = initial_depth = 1;

	root = dad;
	nps[depth] = dad;

	for (offset = 0;
	     offset >= 0 && depth >= initial_depth;
	     offset = fdt_next_node(blob, offset, &depth)) {    //遍历dtb的所有节点
		if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH))
			continue;

		if (!IS_ENABLED(CONFIG_OF_KOBJ) &&    //在sys文件系统也加入设备树节点
		    !of_fdt_device_is_available(blob, offset))
			continue;

        //分配并初始化设备树节点
		if (!populate_node(blob, offset, &mem, nps[depth],
				   &nps[depth+1], dryrun))
			return mem - base;

		if (!dryrun && nodepp && !*nodepp)
			*nodepp = nps[depth+1];
		if (!dryrun && !root)
			root = nps[depth+1];
	}

	if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
		pr_err("Error %d processing FDT\n", offset);
		return -EINVAL;
	}

	/*
	 * Reverse the child list. Some drivers assumes node order matches .dts
	 * node order
	 */
	if (!dryrun)
		reverse_nodes(root);    //建立节点之间的关系,同父节点

	return mem - base;
}




static bool populate_node(const void *blob,
			  int offset,
			  void **mem,
			  struct device_node *dad,
			  struct device_node **pnp,
			  bool dryrun)
{
	struct device_node *np;
	const char *pathp;
	unsigned int l, allocl;

	pathp = fdt_get_name(blob, offset, &l);    //获取设备树节点的名字
	if (!pathp) {
		*pnp = NULL;
		return false;
	}

	allocl = ++l;        //增加结束符

    //从前面一次性为所有设备树节点申请的mem内存中分配一个device_node和名字长度
	np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
				__alignof__(struct device_node));
	if (!dryrun) {
		char *fn;
		of_node_init(np);        //初始化刚分为的设备节点
		np->full_name = fn = ((char *)np) + sizeof(*np);    //设置设备节点名字所在地址

		memcpy(fn, pathp, l);    //把名字放到具体地址

        //建立树形关系
		if (dad != NULL) {
			np->parent = dad;
			np->sibling = dad->child;
			dad->child = np;
		}
	}

    //获取这个节点的属性,并进行初始化设置
	populate_properties(blob, offset, mem, np, pathp, dryrun);
	if (!dryrun) {
        //设置属性"name"的值设置device_node->name,设置属性"device_type"的值设置device_node->type,
		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>";
	}

	*pnp = np;

	return true;
}

//初始化每个设备树节点的基本操作函数
static inline void of_node_init(struct device_node *node)
{
#if defined(CONFIG_OF_KOBJ)
	kobject_init(&node->kobj, &of_node_ktype);
#endif
	node->fwnode.ops = &of_fwnode_ops;
}


const struct fwnode_operations of_fwnode_ops = {
	.get = of_fwnode_get,
	.put = of_fwnode_put,
	.device_is_available = of_fwnode_device_is_available,
	.device_get_match_data = of_fwnode_device_get_match_data,
	.property_present = of_fwnode_property_present,
	.property_read_int_array = of_fwnode_property_read_int_array,
	.property_read_string_array = of_fwnode_property_read_string_array,
	.get_parent = of_fwnode_get_parent,
	.get_next_child_node = of_fwnode_get_next_child_node,
	.get_named_child_node = of_fwnode_get_named_child_node,
	.get_reference_args = of_fwnode_get_reference_args,
	.graph_get_next_endpoint = of_fwnode_graph_get_next_endpoint,
	.graph_get_remote_endpoint = of_fwnode_graph_get_remote_endpoint,
	.graph_get_port_parent = of_fwnode_graph_get_port_parent,
	.graph_parse_endpoint = of_fwnode_graph_parse_endpoint,
};
EXPORT_SYMBOL_GPL(of_fwnode_ops);



//分配属性,设置属性
static void populate_properties(const void *blob,
				int offset,
				void **mem,
				struct device_node *np,
				const char *nodename,
				bool dryrun)
{
	struct property *pp, **pprev = NULL;
	int cur;
	bool has_name = false;

    //遍历np节点中的所有属性,并分配属性和初始化
	pprev = &np->properties;
	for (cur = fdt_first_property_offset(blob, offset);
	     cur >= 0;
	     cur = fdt_next_property_offset(blob, cur)) {
		const __be32 *val;
		const char *pname;
		u32 sz;
        
        //获取属性名字,value和value的大小
		val = fdt_getprop_by_offset(blob, cur, &pname, &sz);
		if (!val) {
			pr_warn("Cannot locate property at 0x%x\n", cur);
			continue;
		}

		if (!pname) {            
			pr_warn("Cannot find property name at 0x%x\n", cur);
			continue;
		}

		if (!strcmp(pname, "name"))    //属性名字为name,这里做一下标记
			has_name = true;

        //从mem内存池分配一个属性结构体
		pp = unflatten_dt_alloc(mem, sizeof(struct property),
					__alignof__(struct property));
		if (dryrun)
			continue;

		/* We accept flattened tree phandles either in
		 * ePAPR-style "phandle" properties, or the
		 * legacy "linux,phandle" properties.  If both
		 * appear and have different values, things
		 * will get weird. Don't do that.
		 */
		if (!strcmp(pname, "phandle") ||    //如果属性名字是phandle,那设置属性的phandle为一个整数值
		    !strcmp(pname, "linux,phandle")) {
			if (!np->phandle)
				np->phandle = be32_to_cpup(val);
		}

		/* And we process the "ibm,phandle" property
		 * used in pSeries dynamic device tree
		 * stuff
		 */
		if (!strcmp(pname, "ibm,phandle"))
			np->phandle = be32_to_cpup(val);

        //初始化属性参数
		pp->name   = (char *)pname;
		pp->length = sz;        //属性值的字节数
		pp->value  = (__be32 *)val;
		*pprev     = pp;
		pprev      = &pp->next;    //属性的同级连接
	}

	/* With version 0x10 we may not have the name property,
	 * recreate it here from the unit name if absent,16版本没name属性,这里就不看了,我们现在都是17版本的
	 */
	if (!has_name) {
		const char *p = nodename, *ps = p, *pa = NULL;
		int len;

		while (*p) {
			if ((*p) == '@')
				pa = p;
			else if ((*p) == '/')
				ps = p + 1;
			p++;
		}

		if (pa < ps)
			pa = p;
		len = (pa - ps) + 1;
		pp = unflatten_dt_alloc(mem, sizeof(struct property) + len,
					__alignof__(struct property));
		if (!dryrun) {
			pp->name   = "name";
			pp->length = len;
			pp->value  = pp + 1;
			*pprev     = pp;
			pprev      = &pp->next;
			memcpy(pp->value, ps, len - 1);
			((char *)pp->value)[len - 1] = 0;
			pr_debug("fixed up name for %s -> %s\n",
				 nodename, (char *)pp->value);
		}
	}

	if (!dryrun)
		*pprev = NULL;
}

 

 

 

设备树总大小的计算分析:

/* First pass, scan for size */
	size = unflatten_dt_nodes(blob, NULL, dad, NULL);
//主要这里mem是NULL !!!!!!!!!!!!!



/**
 * unflatten_dt_nodes - Alloc and populate a device_node from the flat tree
 * @blob: The parent device tree blob
 * @mem: Memory chunk to use for allocating device nodes and properties
 * @dad: Parent struct device_node
 * @nodepp: The device_node tree created by the call
 *
 * It returns the size of unflattened device tree or error code
 */
static int unflatten_dt_nodes(const void *blob,
			      void *mem,
			      struct device_node *dad,
			      struct device_node **nodepp)
{
	struct device_node *root;
	int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH	64
	struct device_node *nps[FDT_MAX_DEPTH];
	void *base = mem;        //即base也为 NULL
	bool dryrun = !base;

	if (nodepp)
		*nodepp = NULL;

	/*
	 * We're unflattening device sub-tree if @dad is valid. There are
	 * possibly multiple nodes in the first level of depth. We need
	 * set @depth to 1 to make fdt_next_node() happy as it bails
	 * immediately when negative @depth is found. Otherwise, the device
	 * nodes except the first one won't be unflattened successfully.
	 */
	if (dad)
		depth = initial_depth = 1;

	root = dad;
	nps[depth] = dad;

	for (offset = 0;
	     offset >= 0 && depth >= initial_depth;
	     offset = fdt_next_node(blob, offset, &depth)) {
		if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH))
			continue;

		if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
		    !of_fdt_device_is_available(blob, offset))
			continue;

        //主要就是这句统计节点的大小,这里mem是取得NULL的地址(0的地址当然也是NULL)
		if (!populate_node(blob, offset, &mem, nps[depth],
				   &nps[depth+1], dryrun))
			return mem - base;

		if (!dryrun && nodepp && !*nodepp)
			*nodepp = nps[depth+1];
		if (!dryrun && !root)
			root = nps[depth+1];
	}

	if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
		pr_err("Error %d processing FDT\n", offset);
		return -EINVAL;
	}

	/*
	 * Reverse the child list. Some drivers assumes node order matches .dts
	 * node order
	 */
	if (!dryrun)
		reverse_nodes(root);

	return mem - base;
}



//这里以及是0的地址了
static bool populate_node(const void *blob,
			  int offset,
			  void **mem,
			  struct device_node *dad,
			  struct device_node **pnp,
			  bool dryrun)
{
	struct device_node *np;
	const char *pathp;
	unsigned int l, allocl;

	pathp = fdt_get_name(blob, offset, &l);    //名称长度
	if (!pathp) {
		*pnp = NULL;
		return false;
	}

	allocl = ++l;    //名称增加结束符

    //具体的增加一个节点的大小
	np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
				__alignof__(struct device_node));
	if (!dryrun) {
		char *fn;
		of_node_init(np);
		np->full_name = fn = ((char *)np) + sizeof(*np);

		memcpy(fn, pathp, l);

		if (dad != NULL) {
			np->parent = dad;
			np->sibling = dad->child;
			dad->child = np;
		}
	}

	populate_properties(blob, offset, mem, np, pathp, dryrun);
	if (!dryrun) {
		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>";
	}

	*pnp = np;
	return true;
}


#define ALIGN(x, a)	(((x) + ((a) - 1)) & ~((a) - 1))

#define PTR_ALIGN(p, a)		((typeof(p))ALIGN((unsigned long)(p), (a)))


static void *unflatten_dt_alloc(void **mem, unsigned long size,
				       unsigned long align)
{
	void *res;

	*mem = PTR_ALIGN(*mem, align);    //这里的mem是&NULL,即*mem第一次进来是0,对齐后还是0。所以*mem = aa代表在0地址赋值

	res = *mem;
	*mem += size;                    //用0地址存放统计的长度

	return res;                      //返回值是从0地址的偏移长度
}

所以这里是在0地址的内存中存放每次统计的数据长度。

 

最后总结一下设备树的节点形成过程。

	setup_arch(&command_line);
        mdesc = setup_machine_fdt(__atags_pointer);


        arm_memblock_init(mdesc);    //保存设备树所在内存区域,以及其它保留的内存区域

    
        unflatten_device_tree();    //解析设备树,组织设备节点等

            __unflatten_device_tree(initial_boot_params, NULL, &of_root,
				early_init_dt_alloc_memory_arch, false);
            
                /* First pass, scan for size,扫描设备节点,统计总的设备树需要的内存大小 */
	            size = unflatten_dt_nodes(blob, NULL, dad, NULL);

                /* Allocate memory for the expanded device tree,申请内存 */
	            mem = dt_alloc(size + 4, __alignof__(struct device_node));


                /* Second pass, do actual unflattening,初始化并建立组织树形关系 */
	            unflatten_dt_nodes(blob, mem, dad, mynodes);
    

 

 

 

 

 

 

 

 

 

 

已标记关键词 清除标记
韦东山老师为啥要录升级版嵌入式视频? 200x年左右,嵌入式Linux在全世界、在中国刚刚兴起。 我记得我2005年进入中兴时,全部门的人正在努力学习Linux。 在2008年,我写了一本书《嵌入式Linux应用开发完全手册》。 它的大概内容是:裸机、U-boot、Linux内核、Linux设备驱动。 那时还没有这样讲解整个系统的书, 芯片厂家Linux开发包也还不完善,从bootloader到内核,再到设备驱动都不完善。 有全系统开发能力的人也很少。 于是这书也就恰逢其时,变成了畅销书。 我也根据这个思路录制了视频:裸机、U-boot、Linux内核、Linux设备驱动。 收获些许名声,带领很多人进入Linux世界。11年过去了,嵌入式Linux世界发生了翻天覆地的变化 ① 基本系统能用 芯片厂家都会提供完整的U-boot、Linux内核、芯片上硬件资源的驱动。 方案厂家会做一些定制,比如加上某个WIFI模块,会添加这个WIFI模块的驱动。 你可以使用厂家的原始方案,或是使用/借鉴方案商的方案,做出一个“能用”的产品。 ② 基础驱动弱化;高级驱动专业化 基础的驱动,比如GPIO、UART、SPI、I2C、LCD、MMC等,有了太多的书籍、视频、示例代码,修修改改总是可以用的。 很多所谓的驱动工程师,实际上就是“调参工程师”。 我们群里有名的火哥,提出了一个概念:这些驱动就起一个“hardware enable”的作用。 高级的驱动,比如USB、PCIE、HDMI、MIPI、GPU、WIFI、蓝牙、摄像头、声卡。 体系非常复杂,很少有人能讲清楚,很多时候只是一笔带过。 配置一下应用层工具就了事,能用就成。 这些高级驱动,工作中需要专门的人来负责,非常专业。 他们是某一块的专家,比如摄像头专家、音频专家。 ③ 项目为王 你到一个公司,目的是把产品做出来,会涉及APP到内核到驱动全流程。 中小公司玩不起华为中兴的配置,需要的是全面手。 大公司里,只负责很小很小一块的镙丝钉,位置也不太稳固啊。 所以,如果你不是立志成为某方面的专家,那就做一个全栈工程师吧。 ④ 调试很重要 都说代码是3分写7分调,各种调试调优技术,可以为你的升职加薪加一把火。 基于上述4点,我录制的全新视频将有这些特点: 1. 快速入门, 2. 实战项目, 3. 驱动大全, 4. 专题, 5. 授人以渔, 6. 要做任务 另外,我们会使用多款芯片同时录制,先讲通用的原理,再单独讲各个板子的操作。 这些芯片涵盖主流芯片公司的主流芯片,让你学习工作无缝对接。 1.快速入门 入门讲究的是快速,入门之后再慢慢深入, 特别是对于急着找工作的学生,对于业余时间挑灯夜读的工作了的人,一定要快! 再从裸机、U-boot、内核、驱动这样的路线学习就不适合了,时间就拉得太长了。 搞不好学了后面忘了前面。 并且实际工作中并不需要你去弄懂U-boot,会用就行:U-boot比驱动还复杂。 讲哪些内容? 怎么讲呢? 混着讲 比如先讲LED APP,知道APP怎么调用驱动,再讲LED硬件原理和裸机,最后讲驱动的编写。 这样可以快速掌握嵌入式Linux的整套开发流程, 不必像以前那样光学习裸机就花上1、2个月。 而里面的裸机课程,也会让你在掌握硬件操作的同时,把单片机也学会了。 讲基础技能 中断、休眠-唤醒、异步通知、阻塞、内存映射等等机制,会配合驱动和APP来讲解。 这些技能是嵌入式Linux开发的基础。 而这些驱动,只会涉及LED、按制、LCD等几个驱动。 掌握了这些输入、输出的驱动和对应的APP后,你已经具备基本的开发能力了。 讲配置 我们从厂家、从方案公司基本上都可以拿到一套完整的开发环境,怎么去配置它? 需要懂shell和python等配置脚本。 效果效率优先 以前我都是现场写代码、现场写文档,字写得慢,降低了学习效率。 这次,效果与效率统一考虑,不再追求所有东西都现场写。 容易的地方可先写好代码文档,难的地方现场写。 2.实战项目 会讲解这样的涉及linux网关/服务器相关项目(不限于,请多提建议):                      定位为:快速掌握项目开发经验,丰满简历。 涉及的每一部分都会讲,比如如果涉及蓝牙,在这里只会讲怎么使用,让你能写出程序;如果要深入,可以看后面的蓝牙专题。 3. 驱动大全 包括基础驱动、高级驱动。 这些驱动都是独立成章,深入讲解。 虽然基础驱动弱化了,但是作为Linux系统开发人员,这是必备技能,并且从驱动去理解内核是一个好方法。 在讲解这些驱动时,会把驱动的运行环境,比如内核调度,进程线程等概念也讲出来,这样就可以搭建一个知识体系。 没有这些知识体系的话,对驱动的理解就太肤浅了,等于在Linux框架下写裸机,一叶障目,不见泰山。 定位为:工具、字典,用到再学习。 4. 专题 想深入学习的任何内容,都可独立为专题。 比如U-boot专题、内核内存管理专题、systemtap调试专题。
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页