你了解linux device tree源代码?

//basedonlinux v3.14 source code
linux设备树机制(device tree)
一、描述
arm device tree起源于openfirmware(of),在过去的linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data。为了改变这种局面,linux社区的大牛们参考了powerpc等体系架构中使用的flattened device tree(fdt),也采用了device tree结构,许多硬件的细节可以直接透过它传递给linux,而不再需要在kernel中进行大量的冗余编码。
device tree是一种描述硬件的数据结构,由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在device tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):cpu的数量和类别,内存基地址和大小,总线和桥,外设连接,中断控制器和中断使用情况,gpio控制器和gpio使用情况,clock控制器和clock使用情况。
通常由.dts文件以文本方式对系统设备树进行描述,经过device tree compiler(dtc)将dts文件转换成二进制文件binary device tree blob(dtb),.dtb文件可由linux内核解析,有了device tree就可以在不改动linux内核的情况下,对不同的平台实现无差异的支持,只需更换相应的dts文件,即可满足。
二、相关结构体
1.u-boot需要将设备树在内存中的存储地址传给内核。该树主要由三大部分组成:头(header)、结构块(structure block)、字符串块(strings block)。设备树在内存中的存储布局图。
------------------------------
base->|struct boot_param_header|
------------------------------
|(alignment gap)(*)|
------------------------------
|memory reserve map|
------------------------------
|(alignment gap)|
------------------------------
||
|device-tree structure|
||
------------------------------
|(alignment gap)|
------------------------------
||
|device-tree strings|
||
----->------------------------------
|
|
---(base+totalsize)
1.1 头(header)
头主要描述设备树的一些基本信息,例如设备树大小,结构块偏移地址,字符串块偏移地址等。偏移地址是相对于设备树头的起始地址计算的。
struct boot_param_header{
__be32 magic;//设备树魔数,固定为0xd00dfeed
__be32 totalsize;//整个设备树的大小
__be32 off_dt_struct;//保存结构块在整个设备树中的偏移
__be32 off_dt_strings;//保存的字符串块在设备树中的偏移
__be32 off_mem_rsvmap;//保留内存区,该区保留了不能被内核动态分配的内存空间
__be32 version;//设备树版本
__be32 last_comp_version;//向下兼容版本号
__be32 boot_cpuid_phys;//为在多核处理器中用于启动的主cpu的物理id
__be32 dt_strings_size;//字符串块大小
__be32 dt_struct_size;//结构块大小
};
1.2 结构块(struct block)
设备树结构块是一个线性化的结构体,是设备树的主体,以节点node的形式保存了目标单板上的设备信息。
在结构块中以宏of_dt_begin_node标志一个节点的开始,以宏of_dt_end_node标识一个节点的结束,整个结构块以宏of_dt_end结束。一个节点主要由以下几部分组成。
(1)节点开始标志:一般为of_dt_begin_node。
(2)节点路径或者节点的单元名(ersion=0x10以节点单元名表示)
(3)填充字段(对齐到四字节)
(4)节点属性。每个属性以宏of_dt_prop开始,后面依次为属性值的字节长度(4字节)、属性名称在字符串块中的偏移量(4字节)、属性值和填充(对齐到四字节)。
(5)如果存在子节点,则定义子节点。
(6)节点结束标志of_dt_end_node。
1.3 字符串块
通过节点的定义知道节点都有若干属性,而不同的节点的属性又有大量相同的属性名称,因此将这些属性名称提取出一张表,当节点需要应用某个属性名称时直接在属性名字段保存该属性名称在字符串块中的偏移量。
1.4 设备树源码 dts 表示
设备树源码文件(.dts)以可读可编辑的文本形式描述系统硬件配置设备树,支持 c/c++方式的注释,该结构有一个唯一的根节点“/”,每个节点都有自己的名字并可以包含多个子节点。设备树的数据格式遵循了 open firmware ieee standard 1275。这个设备树中有很多节点,每个节点都指定了节点单元名称。每一个属性后面都给出相应的值。以双引号引出的内容为 ascii 字符串,以尖括号给出的是 32 位的16进制值。这个树结构是启动 linux 内核所需节点和属性简化后的集合,包括了根节点的基本模式信息、cpu 和物理内存布局,它还包括通过/chosen 节点传递给内核的命令行参数信息。
1.5 machine_desc结构
内核提供了一个重要的结构体struct machine_desc ,这个结构体在内核移植中起到相当重要的作用,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。machine_desc结构体通过machine_start宏来初始化,在代码中, 通过在start_kernel->setup_arch中调用setup_machine_fdt来获取。
struct machine_desc{
unsignedintnr;/*architecture number*/
constchar*name;/*architecture name*/
unsigned long atag_offset;/*tagged list(relative)*/
constchar*const*dt_compat;/*arrayof device tree*'compatible'strings*/
unsignedintnr_irqs;/*number of irqs*/
#ifdef config_zone_dma
phys_addr_t dma_zone_size;/*size of dma-able area*/
#endif
unsignedintvideo_start;/*start of video ram*/
unsignedintvideo_end;/*endof video ram*/
unsigned char reserve_lp0:1;/*never has lp0*/
unsigned char reserve_lp1:1;/*never has lp1*/
unsigned char reserve_lp2:1;/*never has lp2*/
enum reboot_mode reboot_mode;/*default restart mode*/
struct smp_operations*smp;/*smp operations*/
bool(*smp_init)(void);
void(*fixup)(struct tag*,char**,struct meminfo*);
void(*init_meminfo)(void);
void(*reserve)(void);/*reserve mem blocks*/
void(*map_io)(void);/*io mappingfunction*/
void(*init_early)(void);
void(*init_irq)(void);
void(*init_time)(void);
void(*init_machine)(void);
void(*init_late)(void);
#ifdef config_multi_irq_handler
void(*handle_irq)(struct pt_regs*);
#endif
void(*restart)(enum reboot_mode,constchar*);
};
1.6 设备节点结构体
struct device_node{
constchar*name;//设备name
constchar*type;//设备类型
phandle phandle;
constchar*full_name;//设备全称,包括父设备名
structproperty*properties;//设备属性链表
structproperty*deadprops;//removed properties
struct device_node*parent;//指向父节点
struct device_node*child;//指向子节点
struct device_node*sibling;//指向兄弟节点
struct device_node*next;//相同设备类型的下一个节点
struct device_node*allnext;//nextinlist of all nodes
struct proc_dir_entry*pde;//该节点对应的proc
struct kref kref;
unsigned long _flags;
void*data;
#ifdefined(config_sparc)
constchar*path_component_name;
unsignedintunique_id;
struct of_irq_controller*irq_trans;
#endif
};
1.7 属性结构体
structproperty{
char*name;//属性名
intlength;//属性值长度
void*value;//属性值
structproperty*next;//指向下一个属性
unsigned long _flags;//标志
unsignedintunique_id;
};
三、设备树初始化及解析
分析linux内核的源码,可以看到其对扁平设备树的解析流程如下:
(1)首先在内核入口处将从u-boot传递过来的镜像基地址。
(2)通过调用early_init_dt_scan()函数来获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。
(3)根据bootargs,cmd_line等系统引导参数进入start_kernel()函数,进行内核的第二阶段初始化。
(4)调用unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。
(5)内核调用of提供的api函数获取of_allnodes链表信息来初始化内核其他子系统、设备等。
//kernel 初始化的代码(init/main.c)
asmlinkage void __init start_kernel(void)
{
...
//这个setup_arch就是各个架构自己的设置函数,哪个参与了编译就调用哪个,arm架构应当是arch/arm/kernel/setup.c中的 setup_arch。
setup_arch(&command_line);
...
}
void __init setup_arch(char**cmdline_p)
{
conststruct machine_desc*mdesc;
setup_processor();
//setup_machine_fdt函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数
mdesc=setup_machine_fdt(__atags_pointer);//__atags_pointer是bootloader传递参数的物理地址
if(!mdesc)
mdesc=setup_machine_tags(__atags_pointer,__machine_arch_type);
machine_desc=mdesc;
machine_name=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;
strlcpy(cmd_line,boot_command_line,command_line_size);
*cmdline_p=cmd_line;
parse_early_param();
sort(&meminfo.bank,meminfo.nr_banks,sizeof(meminfo.bank[0]),meminfo_cmp,null);
early_paging_init(mdesc,lookup_processor_type(read_cpuid_id()));
setup_dma_zone(mdesc);
sanity_check_meminfo();
arm_memblock_init(&meminfo,mdesc);
paging_init(mdesc);
request_standard_resources(mdesc);
if(mdesc->restart)
arm_pm_restart=mdesc->restart;
//解析设备树
unflatten_device_tree();
......
}
(一)函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数
1.setup_machine_fdt()函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。
conststruct machine_desc*__init setup_machine_fdt(unsignedintdt_phys)
{
conststruct machine_desc*mdesc,*mdesc_best=null;
#ifdef config_arch_multiplatform
dt_machine_start(generic_dt,generic dt based system)
machine_end
mdesc_best=&__mach_desc_generic_dt;
#endif
//bootloader传递参数的物理地址不为空,并将物理地址转化为虚拟地址,
//通过函数early_init_dt_scan从设备树中读出bootargs,cmd_line等系统引导参数。
if(!dt_phys||!early_init_dt_scan(phys_to_virt(dt_phys)))
returnnull;
//根据设备树中根节点属性compatible的属性描述,找到系统中定义的最匹配的machine_desc结构,该结构控制系统体系架构相关部分的初始化
mdesc=of_flat_dt_match_machine(mdesc_best,arch_get_next_mach);
if(!mdesc){
constchar*prop;
long size;
unsigned long dt_root;
early_print(error: unrecognized/unsupported device tree compatible list:[ );
//找到设备树的根节点,dt_root指向根节点的属性地址处
dt_root=of_get_flat_dt_root();
//读出根节点的compatible属性的属性值
prop=of_get_flat_dt_prop(dt_root,compatible,&size);
//将根节点的compatible属性的属性值打印出来
while(size>0){
early_print('%s' ,prop);
size-=strlen(prop)+1;
prop+=strlen(prop)+1;
}
early_print(]);
dump_machine_table();/*doesnotreturn*/
}
//change machine numbertomatch the mdesc we're using
__machine_arch_type=mdesc->nr;
return mdesc;
}
struct boot_param_header*initial_boot_params;
bool __init early_init_dt_scan(void*params)
{
if(!params)
returnfalse;
//参数params是bootloader传递参数的物理地址转化为的虚拟地址,保存设备树起始地址
initial_boot_params=params;
//验证设备树的magic
if(be32_to_cpu(initial_boot_params->magic)!=of_dt_header){
initial_boot_params=null;
returnfalse;
}
//从设备树中读取chosen节点的信息,包括命令行boot_command_line,initrd location及size
of_scan_flat_dt(early_init_dt_scan_chosen,boot_command_line);
//得到根节点的{size,address}-cells信息
of_scan_flat_dt(early_init_dt_scan_root,null);
//读出设备树的系统内存设置
of_scan_flat_dt(early_init_dt_scan_memory,null);
returntrue;
}
int__init of_scan_flat_dt(int(*it)(unsigned long node,constchar*uname,intdepth,void*data),void*data)
{
//找到设备树中结构块的地址
unsigned long p=((unsigned long)initial_boot_params)+be32_to_cpu(initial_boot_params->off_dt_struct);
intrc=0;
intdepth=-1;
do{
//获得节点起始标志,即of_dt_begin_node,of_dt_prop等
u32 tag=be32_to_cpup((__be32*)p);
constchar*pathp;
p+=4;//跳过节点起始标志
//如果是of_dt_end_node标志,表示该节点结束,继续下一结点
if(tag==of_dt_end_node){
depth--;
continue;
}
//of_dt_nop标志代表空节点
if(tag==of_dt_nop)
continue;
//of_dt_end标志整个结构块结束
if(tag==of_dt_end)
break;
//of_dt_prop标示属性,property:属性值的字节长度、属性名称在字符串块中的偏移量、属性值和填充。
if(tag==of_dt_prop){
//属性值的字节大小
u32 sz=be32_to_cpup((__be32*)p);
p+=8;//跳过属性值的字节长度、属性名称在字符串块中的偏移量
if(be32_to_cpu(initial_boot_params->version)=8?8:4);
//跳过该属性值的大小
p+=sz;
//地址对齐
p=align(p,4);
//表示一个属性节点遍历完成,因为这里并不是寻找属性节点,而是找of_dt_begin_node开始的节点
continue;
}
//若都不是以上节点类型,也不是节点开始标示(of_dt_begin_node),则出错返回
if(tag!=of_dt_begin_node){
pr_err(invalid tag %x in flat device tree!,tag);
return-einval;
}
//执行到这里,标示tag=of_dt_begin_node,表示一个节点的开始,探索深度加1
depth++;
//节点路径或者节点名
pathp=(char*)p;
//节点地址四字节对齐,然后p指向节点属性地址
p=align(p+strlen(pathp)+1,4);
//如果是节点路径,则返回路径名的最后一段,假如为/root/my_root,则返回my_root,即获得节点名
if(*pathp=='/')
pathp=kbasename(pathp);
//调用相应的节点处理函数,p指向节点属性地址
rc=it(p,pathp,depth,data);
if(rc!=0)
break;
}while(1);
return rc;
}
1.1 chosen节点
//chosen 节点并不代表一个真实的设备,只是作为一个为固件和操作系统之间传递数据的地方,比如引导参数。chosen 节点里的数据也不代表硬件。通常,chosen 节点在.dts 源文件中为空,并在启动时填充。在我们的示例系统中,固件可以往 chosen 节点添加以下信息:
//chosen{
//bootargs=root=/dev/nfs rw nfsroot=192.168.1.1 console=ttys0,115200;//节点属性
//linux,initrd-start=;//节点属性
//linux,initrd-end=;//节点属性
//};
int__init early_init_dt_scan_chosen(unsigned long node,constchar*uname,intdepth,void*data)
{
unsigned long l;
char*p;
pr_debug(search chosen, depth: %d, uname: %s,depth,uname);
//depth深度要为1,表示在根节点下(一般根节点/的depth为0)
//data表示系统启动命令行boot_command_line要分配空间
//检查节点名是否为chosen节点
if(depth!=1||!data||(strcmp(uname,chosen)!=0&&strcmp(uname,chosen@0)!=0))
return 0;
//从设备树的chosen节点中读出initrd的起始、结束地址
early_init_dt_check_for_initrd(node);
//设备树的chosen节点中读取bootargs属性的属性值,并拷贝给boot_command_line
p=of_get_flat_dt_prop(node,bootargs,&l);
if(p!=null&&l>0)
strlcpy(data,p,min((int)l,command_line_size));
pr_debug(command line is: %s,(char*)data);
return 1;
}
static void __init early_init_dt_check_for_initrd(unsigned long node)
{
u64 start,end;
unsigned longlen;
__be32*prop;
pr_debug(looking for initrd properties... );
//返回该chosen节点中属性名为linux,initrd-start的地址
prop=of_get_flat_dt_prop(node,linux,initrd-start,&len);
if(!prop)
return;
//从该地址读出initrd-start的地址
start=of_read_number(prop,len/4);
//返回该chosen节点中属性名为linux,initrd-end的地址
prop=of_get_flat_dt_prop(node,linux,initrd-end,&len);
if(!prop)
return;
//从该地址读出initrd-end的地址
end=of_read_number(prop,len/4);
//将读出的地址赋值给全局变量initrd_start和initrd_end,用于跟文件系统的挂载
initrd_start=(unsigned long)__va(start);
initrd_end=(unsigned long)__va(end);
initrd_below_start_ok=1;
pr_debug(initrd_start=0x%llx initrd_end=0x%llx,(unsigned long long)start,(unsigned long long)end);
}
void*__init of_get_flat_dt_prop(unsigned long node,constchar*name,unsigned long*size)
{
return of_fdt_get_property(initial_boot_params,node,name,size);
}
void*of_fdt_get_property(struct boot_param_header*blob,unsigned long node,constchar*name,unsigned long*size)
{
//p指向该chosen节点的节点属性地址
unsigned long p=node;
//因为一个节点中可能包含多个属性,所以这里遍历chosen节点中的所有属性,找到属性名为name的属性
do{
//取得该节点属性的起始标志of_dt_prop
u32 tag=be32_to_cpup((__be32*)p);
u32 sz,noff;
constchar*nstr;
p+=4;//跳过节点属性的起始标志
//空节点则继续
if(tag==of_dt_nop)
continue;
//非属性标志则返回null
if(tag!=of_dt_prop)
returnnull;
//运行到这里表示为属性of_dt_prop
//取得该节点属性的的属性值size
sz=be32_to_cpup((__be32*)p);
//取得该属性名的在字符串块中的偏移值
noff=be32_to_cpup((__be32*)(p+4));
p+=8;
//跳过对齐填充字段
if(be32_to_cpu(blob->version)=8?8:4);
//在字符串块取出该属性的名称
nstr=of_fdt_get_string(blob,noff);
if(nstr==null){
pr_warning(can't find property index name !);
returnnull;
}
//若名称一致,表示找到我们要找的属性
if(strcmp(name,nstr)==0){
if(size)
*size=sz;//返回该属性的属性值size
//返回该属性值所在的地址
return(void*)p;
}
//否则继续下一个属性
p+=sz;
p=align(p,4);
}while(1);
}
char*of_fdt_get_string(struct boot_param_header*blob,u32 offset)
{
//从设备树的字符串块的offset处读出name
return((char*)blob)+be32_to_cpu(blob->off_dt_strings)+offset;
}
static inline u64 of_read_number(const__be32*cell,intsize)
{
u64 r=0;
//读出属性值,属性值大小为size
while(size--)
r=(r<=(dt_root_addr_cells+dt_root_size_cells)){
u64 base,size;
//读出物理内存的起始地址以及size
base=dt_mem_next_cell(dt_root_addr_cells,®);
size=dt_mem_next_cell(dt_root_size_cells,®);
if(size==0)
continue;
pr_debug( - %llx , %llx,(unsigned long long)base,(unsigned long long)size);
//添加物理内存到memblock中进行管理,这里不再展开
early_init_dt_add_memory_arch(base,size);
}
return 0;
}
2.通过比较根节点属性compatible值指明和目标板为同一系列的兼容的开发板名称
//compatible制定系统的名称。它包含,格式的字符串。准确地确定器件型号是非常重要的,并且我们需要包含厂商的名字来避免名字空间冲突。因为操作系统会使用compatible这个值来决定怎样在这个机器上运行,所以在这个属性中放入正确的值是非常重要的。
constvoid*__init of_flat_dt_match_machine(constvoid*default_match,
constvoid*(*get_next_compat)(constchar*const**))
{
constvoid*data=null;
constvoid*best_data=default_match;
constchar*const*compat;
unsigned long dt_root;
unsignedintbest_score=~1,score=0;
//读出设备树的根节点,dt_root指向根节点的属性地址处
dt_root=of_get_flat_dt_root();
//调用arch_get_next_mach,遍历该系统中的所有machine_desc结构,返回给data,并且返回该结构的compatible
while((data=get_next_compat(&compat))){
//将系统中的所有machine_desc结构的compatible字符串与设备树根节点的compatible属性进行match
score=of_flat_dt_match(dt_root,compat);
//返回与根节点属性compatible属性值最匹配的machine_desc结构
if(score>0&&score0){
printk('%s' ,prop);
size-=strlen(prop)+1;
prop+=strlen(prop)+1;
}
}
printk(]);
returnnull;
}
pr_info(machine model: %s,of_flat_dt_get_machine_name());
return best_data;
}
//查找设备树的根节点
unsigned long __init of_get_flat_dt_root(void)
{
//找到设备树的设备块起始地址
unsigned long p=((unsigned long)initial_boot_params)+
be32_to_cpu(initial_boot_params->off_dt_struct);
//跳过空节点
while(be32_to_cpup((__be32*)p)==of_dt_nop)
p+=4;
bug_on(be32_to_cpup((__be32*)p)!=of_dt_begin_node);
p+=4;
//第一个节点就是根节点,p指向根节点的属性地址处
return align(p+strlen((char*)p)+1,4);
}
//arch/arm/kernel/devtree.c
__arch_info_begin 和 __arch_info_end是在 arch/arm/kernel/vmlinux.lds.s中:
00034:__arch_info_begin=.;
00035:*(.arch.info.init)
00036:__arch_info_end=.;
这里是声明了两个变量:__arch_info_begin 和 __arch_info_end,其中等号后面的.是location counter。在__arch_info_begin 的位置上,放置所有文件中的.arch.info.init段的内容,然后紧接着是 __arch_info_end 的位置..arch.info.init段中定义了设备的machine_desc结构。
//这里就是取出一个machine_desc结构
staticconstvoid*__init arch_get_next_mach(constchar*const**match)
{
staticconststruct machine_desc*mdesc=__arch_info_begin;
conststruct machine_desc*m=mdesc;
if(m>=__arch_info_end)
returnnull;
mdesc++;//指针后移,确保下次取出下一个machine_desc结构
*match=m->dt_compat;
return m;//返回当前的machine_desc结构
}
//与设备树根节点进行match
int__init of_flat_dt_match(unsigned long node,constchar*const*compat)
{
//initial_boot_params指向设备树起始地址
//node指向根节点的属性地址
//compat为系统中machine_desc结构的compatible字符串
return of_fdt_match(initial_boot_params,node,compat);
}
intof_fdt_match(struct boot_param_header*blob,unsigned long node,constchar*const*compat)
{
unsignedinttmp,score=0;
if(!compat)
return 0;
//遍历compatible字符串数组
while(*compat){
//返回compatible的匹配值
tmp=of_fdt_is_compatible(blob,node,*compat);
if(tmp&&(score==0||(tmp0){
score++;
if(of_compat_cmp(cp,compat,strlen(compat))==0)
return score;
l=strlen(cp)+1;
cp+=l;
cplen-=l;
}
return 0;
}
(二)、解析设备树
//unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。内核调用of提供的api函数获取of_allnodes链表信息来初始化内核其他子系统、设备。
void __init unflatten_device_tree(void)
{
//解析设备树,将所有的设备节点链入全局链表of_allnodes中
__unflatten_device_tree(initial_boot_params,&of_allnodes,early_init_dt_alloc_memory_arch);
//设置内核输出终端,以及遍历“/aliases”节点下的所有的属性,挂入相应链表
of_alias_scan(early_init_dt_alloc_memory_arch);
}
static void __unflatten_device_tree(struct boot_param_header*blob,
struct device_node**mynodes,
void*(*dt_alloc)(u64 size,u64 align))
{
unsigned long size;
void*start,*mem;
struct device_node**allnextp=mynodes;
pr_debug( -> unflatten_device_tree());
if(!blob){
pr_debug(no device tree pointer);
return;
}
pr_debug(unflattening device tree:);
pr_debug(magic: %08x,be32_to_cpu(blob->magic));
pr_debug(size: %08x,be32_to_cpu(blob->totalsize));
pr_debug(version: %08x,be32_to_cpu(blob->version));
//检查设备树magic
if(be32_to_cpu(blob->magic)!=of_dt_header){
pr_err(invalid device tree blob header);
return;
}
//找到设备树的设备节点起始地址
start=((void*)blob)+be32_to_cpu(blob->off_dt_struct);
//第一次调用mem传0,allnextpp传null,实际上是为了计算整个设备树所要的空间
size=(unsigned long)unflatten_dt_node(blob,0,&start,null,null,0);
size=align(size,4);//4字节对齐
pr_debug( size is %lx, allocating...,size);
//调用early_init_dt_alloc_memory_arch函数,为设备树分配内存空间
mem=dt_alloc(size+4,__alignof__(struct device_node));
memset(mem,0,size);
//设备树结束处赋值0xdeadbeef,为了后边检查是否有数据溢出
*(__be32*)(mem+size)=cpu_to_be32(0xdeadbeef);
pr_debug( unflattening %p...,mem);
//再次获取设备树的设备节点起始地址
start=((void*)blob)+be32_to_cpu(blob->off_dt_struct);
//mem为设备树分配的内存空间,allnextp指向全局变量of_allnodes,生成整个设备树
unflatten_dt_node(blob,mem,&start,null,&allnextp,0);
if(be32_to_cpup(start)!=of_dt_end)
pr_warning(weird tag at end of tree: %08x,be32_to_cpup(start));
if(be32_to_cpup(mem+size)!=0xdeadbeef)
pr_warning(end of tree marker overwritten: %08x,be32_to_cpup(mem+size));
*allnextp=null;
pr_debug( full_name=fn=((char*)np)+sizeof(*np);
//若new_format=1,表示pathp保存的是节点名,而不是节点路径名,所以需要加上父节点的name
if(new_format){
if(dad&&dad->parent){
strcpy(fn,dad->full_name);//把父亲节点绝对路径先拷贝
fn+=strlen(fn);
}
*(fn++)='/';
}
memcpy(fn,pathp,l);//拷贝本节点的名称
//prev_pp指向节点的属性链表
prev_pp=&np->properties;
//当前节点插入全局链表of_allnodes
**allnextpp=np;
*allnextpp=&np->allnext;
//若父亲节点不为空,则设置该节点的parent
if(dad!=null){
np->parent=dad;//指向父亲节点
if(dad->next==null)//第一个孩子
dad->child=np;//child指向第一个孩子
else
dad->next->sibling=np;//把np插入next,这样孩子节点形成链表
dad->next=np;
}
kref_init(&np->kref);
}
//分析该节点的属性
while(1){
u32 sz,noff;
char*pname;
//前边已经将*p移到指向节点属性的地址处,取出属性标识
tag=be32_to_cpup(*p);
//空属性,则跳过
if(tag==of_dt_nop){
*p+=4;
continue;
}
//tag不是属性则退出,对于有孩子节点退出时为of_dt_begin_node,对于叶子节点退出时为of_dt_end_node
if(tag!=of_dt_prop)
break;
//地址加4,跳过tag
*p+=4;
//获得属性值的大小,是以为占多少整形指针计算的
sz=be32_to_cpup(*p);
//获取属性名称在节点的字符串块中的偏移
noff=be32_to_cpup(*p+4);
//地址加8,跳过属性值的大小和属性名称在节点的字符串块中的偏移
*p+=8;
//地址对齐后,*p指向属性值所在的地址
if(be32_to_cpu(blob->version)=8?8:4);
//从节点的字符串块中noff偏移处,得到该属性的name
pname=of_fdt_get_string(blob,noff);
if(pname==null){
pr_info(can't find property name in list !);
break;
}
//如果有名称为name的属性,表示变量has_name为1
if(strcmp(pname,name)==0)
has_name=1;
//计算该属性name的大小
l=strlen(pname)+1;
//为该属性分配一个属性结构,即struct property,
//*mem记录分配了多大空间,最终会累加计算出该设备树总共分配的空间大小
pp=unflatten_dt_alloc(&mem,sizeof(structproperty),__alignof__(structproperty));
//第一次调用unflatten_dt_node时,allnextpp=null
//第一次调用unflatten_dt_node时,allnextpp指向全局变量of_allnodes的地址
if(allnextpp){
if((strcmp(pname,phandle)==0)||(strcmp(pname,linux,phandle)==0)){
if(np->phandle==0)
np->phandle=be32_to_cpup((__be32*)*p);
}
if(strcmp(pname,ibm,phandle)==0)
np->phandle=be32_to_cpup((__be32*)*p);
pp->name=pname;//属性名
pp->length=sz;//属性值长度
pp->value=*p;//属性值
//属性插入该节点的属性链表np->properties
*prev_pp=pp;
prev_pp=&pp->next;
}
*p=ptr_align((*p)+sz,4);//指向下一个属性
}
//至此遍历完该节点的所有属性
//如果该节点没有name的属性,则为该节点生成一个name属性,插入该节点的属性链表
if(!has_name){
char*p1=pathp,*ps=pathp,*pa=null;
intsz;
while(*p1){
if((*p1)=='@')
pa=p1;
if((*p1)=='/')
ps=p1+1;
p1++;
}
if(paname=name;
pp->length=sz;
pp->value=pp+1;
*prev_pp=pp;
prev_pp=&pp->next;
memcpy(pp->value,ps,sz-1);
((char*)pp->value)[sz-1]=0;
pr_debug(fixed up name for %s -> %s,pathp,(char*)pp->value);
}
}
//若设置了allnextpp指针
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=;
if(!np->type)
np->type=;
}
//前边在遍历属性时,tag不是属性则退出
//对于有孩子节点退出时tag为of_dt_begin_node,对于叶子节点退出时tag为of_dt_end_node
while(tag==of_dt_begin_node||tag==of_dt_nop){
//空属性则指向下个属性
if(tag==of_dt_nop)
*p+=4;
else
//of_dt_begin_node则表明其还有子节点,所以递归分析其子节点
mem=unflatten_dt_node(blob,mem,p,np,allnextpp,fpsize);
tag=be32_to_cpup(*p);
}
//对于叶子节点或者分析完成
if(tag!=of_dt_end_node){
pr_err(weird tag at end of node: %x,tag);
return mem;
}
*p+=4;
//mem返回整个设备树所分配的内存大小,即设备树占的内存空间
return mem;
}
//从mem分配内存空间,*mem记录分配了多大空间
static void*unflatten_dt_alloc(void**mem,unsigned long size,unsigned long align)
{
void*res;
*mem=ptr_align(*mem,align);
res=*mem;
*mem+=size;
return res;
}
//一个特定的节点通常是以完整的路径来引用,比如/external-bus/ethernet@0,0,不过当一个用户真的想知道“哪个设备是eth0”时,这将会很繁琐。aliases节点可以用来为一个完整的设备路径分配一个短的别名。比如:
//aliases{
//serial0=&uart0;
//serial1=&uart1;
//serial2=&uart2;
//serial3=&uart3;
//ethernet0=ð0;
//serial0=&serial0;
//};
//当需要为设备指定一个标示符时,操作系统欢迎大家使用别名。
//设置内核输出终端,以及遍历“/aliases”节点下的所有的属性,挂入相应链表
void of_alias_scan(void*(*dt_alloc)(u64 size,u64 align))
{
structproperty*pp;
//根据全局的device_node结构的链表of_allnodes,查找节点名为“/chosen”或者“/chosen@0”的节点,赋值给全局变量of_chosen
of_chosen=of_find_node_by_path(/chosen);
if(of_chosen==null)
of_chosen=of_find_node_by_path(/chosen@0);
//找到的话,则在该节点查找linux,stdout-path属性
//linux,stdout-path的属性值,常常为标准终端设备的节点路径名,内核会以此作为默认终端
if (of_chosen) {
const char *name;
//返回属性linux,stdout-path的属性值
name = of_get_property(of_chosen, linux,stdout-path, null);
//根据属性值查找设备节点device_node,即内核默认终端的设备节点,赋值给全局变量of_stdout
if (name)
of_stdout = of_find_node_by_path(name);
}
//据全局链表of_allnodes,查找节点名为“/aliases”的节点,赋值给全局变量of_aliases
of_aliases = of_find_node_by_path(/aliases);
if (!of_aliases)
return;
//遍历“/aliases”节点下的所有的属性
for_each_property_of_node(of_aliases, pp) {
const char *start = pp->name;//属性名
const char *end = start + strlen(start);//属性名结尾
struct device_node *np;
struct alias_prop *ap;
int id, len;
//跳过name、phandle和linux,phandle的属性
if (!strcmp(pp->name, name) ||
!strcmp(pp->name, phandle) ||
!strcmp(pp->name, linux,phandle))
continue;
//根据属性值找到对应的设备节点
np = of_find_node_by_path(pp->value);
if (!np)
continue;
//去除属性名中结尾的数字,即设备id
while (isdigit(*(end-1)) && end > start)
end--;
//len为属性名去掉结尾数字序号的长度
len = end - start;
//此时end指向属性名中结尾的数字,即开始时start指向“&uart0”,end指向字符串结尾。
//经过上步操作,start仍指向“&uart0”字符串开始处,而end指向字符‘0’。
//将end字符串转化为10进制数,赋值给id,作为设备的id号
if (kstrtoint(end, 10, &id) alias = start;
//将该设备的aliases指向对应的device_node,并且链入aliases_lookup链表中
of_alias_add(ap, np, id, start, len);
}
}
四、of提供的常用api函数
//of提供的函数主要集中在drivers/of/目录下,有address.c,base.c,device.c,fdt.c,irq.c,platform.c等等
1. 用来查找在dtb中的根节点
unsigned long __init of_get_flat_dt_root(void)
2. 根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node
struct device_node *of_find_node_by_path(const char *path)
例如:
struct device_node *cpus;
cpus=of_find_node_by_path(/cpus);
3. 若from=null,则在全局链表of_allnodes中根据name查找合适的device_node
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
例如:
struct device_node *np;
np = of_find_node_by_name(null,firewire);
4. 根据设备类型查找相应的device_node
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)
例如:
struct device_node *tsi_pci;
tsi_pci= of_find_node_by_type(null,pci);
5. 根据compatible字符串查找device_node
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)
6. 根据节点属性的name查找device_node
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)
7. 根据phandle查找device_node
struct device_node *of_find_node_by_phandle(phandle handle)
8. 根据alias的name获得设备id号
int of_alias_get_id(struct device_node *np, const char *stem)
9. device node计数增加/减少
struct device_node *of_node_get(struct device_node *node)
void of_node_put(struct device_node *node)
10. 根据property结构的name参数,在指定的device node中查找合适的property
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
11. 根据property结构的name参数,返回该属性的属性值
const void *of_get_property(const struct device_node *np, const char *name,int *lenp)
12. 根据compat参数与device node的compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)
13. 获得父节点的device node
struct device_node *of_get_parent(const struct device_node *node)
14. 将matches数组中of_device_id结构的name和type与device node的compatible和type匹配,返回匹配度最高的of_device_id结构
const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node)
15. 根据属性名propname,读出属性值中的第index个u32数值给out_value
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value)
16. 根据属性名propname,读出该属性的数组中sz个属性值给out_values
int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
int of_property_read_u16_array(const struct device_node *np,const char *propname, u16 *out_values, size_t sz)
int of_property_read_u32_array(const struct device_node *np,const char *propname, u32 *out_values,size_t sz)
17. 根据属性名propname,读出该属性的u64属性值
int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value)
18. 根据属性名propname,读出该属性的字符串属性值
int of_property_read_string(struct device_node *np, const char *propname,const char **out_string)
19. 根据属性名propname,读出该字符串属性值数组中的第index个字符串
int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output)
20. 读取属性名propname中,字符串属性值的个数
int of_property_count_strings(struct device_node *np, const char *propname)
21. 读取该设备的第index个irq号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
22. 读取该设备的第index个irq号,并填充一个irq资源结构体
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
23. 获取该设备的irq个数
int of_irq_count(struct device_node *dev)
24. 获取设备寄存器地址,并填充寄存器资源结构体
int of_address_to_resource(struct device_node *dev, int index,struct resource *r)
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags)
25. 获取经过映射的寄存器虚拟地址
void __iomem *of_iomap(struct device_node *np, int index)
24. 根据device_node查找返回该设备对应的platform_device结构
struct platform_device *of_find_device_by_node(struct device_node *np)
25. 根据device node,bus id以及父节点创建该设备的platform_device结构
struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,
void *platform_data,struct device *parent)
26. 遍历of_allnodes中的节点挂接到of_platform_bus_type总线上,由于此时of_platform_bus_type总线上还没有驱动,所以此时不进行匹配
int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent)
27. 遍历of_allnodes中的所有节点,生成并初始化platform_device结构
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(/

HLS与RTL语言的使用最佳实践研究
礼来AI自动执行不良反应报告
核磁共振是什么?磁场强度大有没有副作用?
网友热烈推荐美肌科技,UGS 优肌诗美容仪让人爱不释手
恒定电流知识点总结
你了解linux device tree源代码?
助焊剂的喷头堵了怎么办,可有什么处理方法
iPhone11系列首批用户如何评价该手机
魅族旗舰 PRO 6采用了 Synaptics 压感触控技术
在自动驾驶中,如何进行汽车毫米波雷达测试?(一)
Android10在功能上做出了哪些升级
最新款的Macbook Air在旧款的基础上进行了以下升级
FIIL上榜最受关注TWS耳机
分布式光伏发电前景
Vishay推600W高浪涌表面贴装TransZorb TVS
Mycobot机械臂各关节的运动(三)
一氧化碳气体检测仪性能如何?详细介绍
苹果13pro发布时间
目前锂电池支持的充电倍率只有多少C?
TA7325P引脚功能及电压资料参数