奇小葩讲设备树(4/5)-- Linux设备树详解(四)kernel的解析
uboot将一些参数,设备树文件传给内核,那么内核如何处理这些设备树文件呢?本章就kernel解析设备树的过程和原理,本章的主要内容以Device Tree相关的数据流分析为索引,对ARM linux kernel的代码进行解析。主要的数据流包括:
- 设备树对于内核的意义
- 从u-boot传递dtb开始,kernel初始化流程,如何将dtb并将其转换成Device Tree Structure
- 传递运行时参数传递以及platform的识别流程分析
- 如何将Device Tree Structure并入linux kernel的设备驱动模型。
1. 设备树的作用
作用 | 详细描述 |
---|---|
平台标识 | 告诉内核dtb支持哪些平台 ; 用DT 来标识特定的machine ; root 节点的compatible 字段,匹配machine_desc的dt_compat |
运行时配置 | chosen节点的属性 |
设备信息集合 | 传递各种设备信息 |
2. 初始化流程
从上一章我们已经知道fdt的地址是作为参数传递到kernel。下面看一下kernel阶段怎么获取这个地址值的。bootloader启动内核时,会设置r0,r1,r2三个寄存器,
r0一般设置为0;
r1一般设置为machine id (在使用设备树时该参数没有被使用);
r2一般设置ATAGS或DTB的开始地址;
对于启动的流程代码如下:
ENTRY(stext)ARM_BE8(setend be ) @ ensure we are in BE8 modeTHUMB( adr r9, BSYM(1f) ) @ Kernel is always entered in ARM.THUMB( bx r9 ) @ If this is a Thumb-2 kernel,THUMB( .thumb ) @ switch to Thumb now.THUMB(1: )#ifdef CONFIG_ARM_VIRT_EXTbl __hyp_stub_install
#endif@ ensure svc mode and all interrupts maskedsafe_svcmode_maskall r9mrc p15, 0, r9, c0, c0 @ get processor idbl __lookup_processor_type @ r5=procinfo r9=cpuidmovs r10, r5 @ invalid processor (r5=0)?THUMB( it eq ) @ force fixup-able long branch encodingbeq __error_p @ yes, error 'p'#ifdef CONFIG_ARM_LPAEmrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0and r3, r3, #0xf @ extract VMSA supportcmp r3, #5 @ long-descriptor translation table format?THUMB( it lo ) @ force fixup-able long branch encodingblo __error_lpae @ only classic page table format
#endif#ifndef CONFIG_XIP_KERNELadr r3, 2fldmia r3, {r4, r8}sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)add r8, r8, r4 @ PHYS_OFFSET
#elseldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
#endif/** r1 = machine no, r2 = atags or dtb,* r8 = phys_offset, r9 = cpuid, r10 = procinfo*/bl __vet_atags
#ifdef CONFIG_SMP_ON_UPbl __fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRTbl __fixup_pv_table
#endifbl __create_page_tables/** The following calls CPU specific code in a position independent* manner. See arch/arm/mm/proc-*.S for details. r10 = base of* xxx_proc_info structure selected by __lookup_processor_type* above. On return, the CPU will be ready for the MMU to be* turned on, and r0 will hold the CPU control register value.*/ldr r13, =__mmap_switched @ address to jump to after@ mmu has been enabledadr lr, BSYM(1f) @ return (PIC) addressmov r8, r4 @ set TTBR1 to swapper_pg_dirARM( add pc, r10, #PROCINFO_INITFUNC )THUMB( add r12, r10, #PROCINFO_INITFUNC )THUMB( ret r12 )
1: b __enable_mmu
ENDPROC(stext).ltorg
#ifndef CONFIG_XIP_KERNEL
2: .long ..long PAGE_OFFSET
#endif
- __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
- __vet_atags : 判断是否存在可用的ATAGS或DTB
在汇编的阶段,大概可以看出来用变量__atags_pointer指向FDT的首地址,执行完汇编的阶段就会调到C代码的流程里面了
3. 平台信息的处理(machine_desc)
asmlinkage void __init start_kernel(void)
{...setup_arch(&command_line); //设置架构相关的内容...
}
由于涉及的知识点内容实在太多,那么我们只是重点的关注fdt的处理,直接进到setup_ arch()函数。
void __init setup_arch(char **cmdline_p)
{
...mdesc = setup_machine_fdt(__atags_pointer);if (!mdesc)mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);machine_desc = mdesc;machine_name = mdesc->name;
...
}
首先通过set_machine_fdt来set_machine描述符,如果返回值是NULL,那么就采用传统的方式,如果u-boot传递了,就采用设备树方式
- 传统方式:对于如何确定mdesc,旧的方法是静态定义若干的machine描述符(struct machine_desc),在系统启动的时候,通过machine type ID作为索引,在这些静态定义的machine描述符中,找到对应哪个ID匹配的描述符。
- 设备树:通过__atags_pointer来找到对应的machine_desc设备描述符
首先我们来看看struct machine_desc的定义方式:
struct machine_desc {
...unsigned int nr; /* architecture number */const char *name; /* architecture name */unsigned long atag_offset; /* tagged list (relative) */const char *const *dt_compat; /* array of device tree
...
}
nr成员就是过去使用的machine type ID。内核machine描述符的table有若干个entry,每个都有自己的ID。bootloader传递了machine type ID,指明使用哪一个machine描述符。而dtb方式中目前匹配machine描述符使用compatible strings,也就是dt_compat成员,这是一个string list,定义了这个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_verify(phys_to_virt(dt_phys)))return NULL;mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);if (!mdesc) {const char *prop;int size;unsigned long dt_root;early_print("\nError: unrecognized/unsupported ""device tree compatible list:\n[ ");dt_root = of_get_flat_dt_root();prop = of_get_flat_dt_prop(dt_root, "compatible", &size);while (size > 0) {early_print("'%s' ", prop);size -= strlen(prop) + 1;prop += strlen(prop) + 1;}early_print("]\n\n");dump_machine_table(); /* does not return */}/* We really don't want to do this, but sometimes firmware provides buggy data */if (mdesc->dt_fixup)mdesc->dt_fixup();early_init_dt_scan_nodes();/* Change machine number to match the mdesc we're using */__machine_arch_type = mdesc->nr;return mdesc;
}
setup_machine_fdt函数的功能就是根据Device Tree的信息,找到最适合的machine描述符。其主要做了下面几件事情
- 传进来的fdt地址是物理地址,所以用phys_to_virt()函数转换为虚拟地址,同时进行合法检测
- 在machine描述符的列表中scan,找到最合适的那个machine描述符。和传统的方法类似,也是静态定义的。DT_MACHINE_START和MACHINE_END用来定义一个machine描述符。编译的时候,compiler会把这些machine descriptor放到一个特殊的段中(.arch.info.init),形成machine描述符的列表。
- of_get_flat_dt_prop(dt_root, “compatible”, &size)使用compatile属性的值, 跟’’‘每一个machine_desc.dt_compat’’'比较,
成绩为"吻合的compatile属性值的位置",成绩越低越匹配, 对应的machine_desc即被选中
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。具体匹配的算法倒是很简单,就是比较字符串而已,最终找到对应的machine type。 从该流程可以知道,内核是可以支持很多种不同类型的设备,只要在bootloader传递的时候,传递对应不同的dtb表即可。
4. 运行时参数传递
void __init early_init_dt_scan_nodes(void)
{/* Retrieve various information from the /chosen node */of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);/* Initialize {size,address}-cells info */of_scan_flat_dt(early_init_dt_scan_root, NULL);/* Setup memory, calling early_init_dt_add_memory_arch */of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
chosen {bootargs = "earlycon=sprd_serial,0x70100000,115200n8 loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw androidboot.hardware=sc9830";linux,initrd-start = <0x85500000>;linux,initrd-end = <0x855a3212>;};
bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来
- 扫描根节点,获取 {size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中 ,memory中的reg属性的地址是32位还是64位,大小是用一个32位表示,还是两个32位表示
- 扫描DTB中的memory node,并把相关信息保存在meminfo中,全局变量meminfo通过memblock_add保存了系统内存相关的信息
5. dtb解析成device node
void __init early_init_fdt_scan_reserved_mem(void)
{int n;u64 base, size;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);/* Process header /memreserve/ fields */for (n = 0; ; n++) {fdt_get_mem_rsv(initial_boot_params, n, &base, &size);if (!size)break;early_init_dt_reserve_memory_arch(base, size, 0);}of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);fdt_init_reserved_mem();
}
- initial_boot_params实际上是dtb的虚拟地址,在early_init_dt_verify初始化的时候设定,首先进来就判断dtb是否存在,如果存在就将dtb的空间进行保留
- 对fdt中的每一个节点调用__fdt_scan_reserved_mem函数,进行reserved-memory节点的扫描,之后调用fdt_init_reserved_mem函数进行内存预留的动作
说完了dtb对于内存的流程,那么来到这节的重点,dtb解析成device node,首先来看看下面的代码
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 {const char *name; //device node name const char *type; //对应device_type的属性 phandle phandle; //对应该节点的phandle属性const char *full_name; //从“/”开始的,表示该node的full pathstruct 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下一个nodestruct kobject kobj;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
}
6. linux kernel的设备驱动模型
static int __init customize_machine(void)
{/** customizes platform devices, or adds new ones* On DT based machines, we fall back to populating the* machine from the device tree, if no callback is provided,* otherwise we would always need an init_machine callback.*/if (machine_desc->init_machine)machine_desc->init_machine();
#ifdef CONFIG_OFelseof_platform_populate(NULL, of_default_bus_match_table,NULL, NULL);
#endifreturn 0;
}
const struct of_device_id of_default_bus_match_table[] = {{ .compatible = "simple-bus", },
#ifdef CONFIG_ARM_AMBA{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */{} /* Empty terminated list */
};
ap-apb {compatible = "simple-bus";#address-cells = <1>;#size-cells = <1>;ranges;uart0: serial@70000000 {compatible = "sprd,sc9836-uart";reg = <0x70000000 0x100>;interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;clock-names = "uart", "source","enable";clocks = <&clk_uart0>, <&ext_26m>,<&clk_ap_apb_gates 13>;status = "disabled";};
}
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;
}
- 获取根节点,如果传递进来的参数root为NULL,那么需要通过of_find_node_by_path函数找到device tree中的根节点。
- 得到根节点之后,就可以通过这个根节点来遍历device tree中的节点了。得到一个子节点之后,调用of_platform_bus_create函数为每一个节点创建platform_device结构体
static int of_platform_bus_create(struct device_node *bus,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent, bool 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;/* Make sure it has a compatible property */if (strict && (!of_get_property(bus, "compatible", NULL))) {pr_debug("%s() - skipping %s, no compatible prop\n",__func__, bus->full_name);return 0;}auxdata = of_dev_lookup(lookup, bus);if (auxdata) {bus_id = auxdata->name;platform_data = auxdata->platform_data;}if (of_device_is_compatible(bus, "arm,primecell")) {/** Don't return an error here to keep compatibility with older* device tree files.*/of_amba_device_create(bus, bus_id, platform_data, parent);return 0;}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_node_set_flag(bus, OF_POPULATED_BUS);return rc;
}
- 需要确定节点是否有"compatible"属性,如果没有"compatible"属性,则直接返回,即不会创建platform设备的。
- 如果"compatible"属性值有"arm,primecell",则会调用of_amba_device_create函数去创建amba_device,它设计了AMBA的总线来连接SOC内的各个block。符合这个总线标准的SOC上的外设叫做ARM Primecell Peripherals
- 如果不是ARM Primecell Peripherals,那么我们就需要向platform bus上增加一个platform device了,of_platform_device_create_pdata才是真正的platform_device
- 一个device node可能是一个桥设备,因此要重复调用of_platform_bus_create来把所有的device node处理掉
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) ||of_node_test_and_set_flag(np, OF_POPULATED))return NULL;dev = of_device_alloc(np, bus_id, parent);if (!dev)goto err_clear_flag;of_dma_configure(&dev->dev);dev->dev.bus = &platform_bus_type;dev->dev.platform_data = platform_data;/* We do not fill the DMA ops for platform devices by default.* This is currently the responsibility of the platform code* to do such, possibly using a device notifier*/if (of_device_add(dev) != 0) {platform_device_put(dev);goto err_clear_flag;}return dev;err_clear_flag:of_node_clear_flag(np, OF_POPULATED);return NULL;
}
- of_device_is_available函数,这个函数主要是用于检测"status"属性的,如果没有"status"属性,那还好说直接返回true。如果有"status"属性,而它的值又不是"okay"或"ok",那么不好意思,返回false,否则还是返回true。所以"status"属性就是用来检测是否可用,是否需要创建platform_node
- of_device_alloc除了分配struct platform_device的内存,还分配了该platform device需要的resource的内存。当然,这就需要解析该device node的interrupt资源以及memory address资源。
- 回到of_platform_device_create_pdata函数中,平台设备已经申请好了,然后对平台设备继续进行赋值操作,例如平台设备的总线赋值为平台总线,平台设备的私有数据赋值为platform_data,最终会调用of_device_add函数将平台设备注册到内核中。
也就是说当of_platform_populate()函数执行完毕,kernel就为DTB中所有包含compatible属性名的第一级node创建platform_device结构体,并向平台设备总线注册设备信息。如果第一级node的compatible属性值等于“simple-bus”、“simple-mfd”或者"arm,amba-bus"的话,kernel会继续为当前node的第二级包含compatible属性的node创建platform_device结构体,并注册设备。Linux系统下的设备大多都是挂载在平台总线下的,因此在平台总线被注册后,会根据of_root节点的树结构,去寻找该总线的子节点,所有的子节点将被作为设备注册到该总线上。
7 参考文档
奇小葩讲设备树(4/5)-- Linux设备树详解(四)kernel的解析相关推荐
- 奇小葩讲设备树(5/5)-- Linux设备树详解(五)设备树的使用
对于任何的知识来说,了解了理论的知识,知道了设备树怎么解析用以代替传统的范式之后,我们需要知道怎么使用设备树.对于使用我们分两部分,一部分是它有哪些接口,能做些什么,至于怎么编写dts文件本章不讨论. ...
- 奇小葩讲设备树(1/5)-- Linux设备树详解(一) 基础知识
关于设备树,之前就经过详细的系统培训,但是本着会用就行的原则,对各个知识点都没有进行系统的总结.都是用到哪里学哪里,时间长了,基本也忘记了.所以对于后期知识各个知识点进行总结,本章主要讨论一下内容,能 ...
- 奇小葩讲设备树(3/5)-- Linux设备树详解(三)u-boot设备树的传递
前面两节介绍了设备的基本概念.编译.结构的组成,本章讨论的主要内容为 dtb如何通过Bootloader引导程序加载到内核 bootloader如何解析dbt bootloader支持哪些dtb的操作 ...
- 奇小葩讲设备树(2/5)-- Linux设备树详解(二)文件构成
设备树就是描述单板资源以及设备的一种文本文件.至于出现的原因,基本的语法和使用方法,上一章节做了基本的介绍.本篇文章主要是更深层次的探讨设备文件的构成. 1. devie tree的编译 Device ...
- 树链剖分之重链剖分详解
树链剖分之重链剖分详解 一些概念 算法讲解 应用 求最近公共祖先 对树上的一条链进行修改和查询 相关练习题 一些概念 在学习重链剖分前,首先要明白以下几个概念: 中二重儿子:就是一个节点的儿子中最&q ...
- 目录树 删除 数据结构_数据结构:B树和B+树的插入、删除图文详解
B树 1.1B树的定义 B树也称B-树,它是一颗多路平衡查找树.我们描述一颗B树时需要指定它的阶数,阶数表示了一个结点最多有多少个孩子结点,一般用字母m表示阶数.当m取2时,就是我们常见的二叉搜索树. ...
- 数据结构图文解析之:哈夫曼树与哈夫曼编码详解及C++模板实现
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
- 数据结构:B树和B+树的插入、删除图文详解
目录 B树 1.1B树的定义 1.2B树的插入操作 1.3B树的删除操作 B+树 2.1 B+树的定义 2.2 B+树的插入操作 2.3 B+树的删除操作 B树 1.1B树的定义 B树也称B-树,它是 ...
- 第二讲:ADS入门和Data DisPlay操作详解
第二讲:ADS入门和Data DisPlay操作详解 设计流程简介 创建Workspace的过程和设计讲解 仿真设计要素和原理图 元件面板和元器件操作 仿真控件 仿真分析设置和运行仿真分析 查看分析结 ...
最新文章
- java 使用正则表达式过滤HTML中标签
- sqlserver 获取当前年_CVE-2020-0618: 微软 SQL Server 远程代码执行漏洞通告
- 怎么去除标题_未来健康家:怎么快速祛除甲醛
- Java绘图模式概述
- 【附可运行代码】剑指 Offer 12. 矩阵中的路径
- Redis轻松实现秒杀系统
- 测视力距离5米还是3米_装B冷知识 | 小孔镜为什么可以提高视力?
- VC中,如何将十六进制字符串转换为十进制数?
- qt菜单栏按钮点击事件_如何用Axure画出Web后台产品的菜单栏组件
- Python path
- 离散数学期末复习总结
- python编入小学教材_之前纳入小学教材的Python,现在真能学会了!
- PS批处理生成EXE格式
- 网易公开课 计算机专业课程
- mysql数据库实训总结_数据库实训报告
- 【flask高级】结合源码解决flask经典报错:Working outside of application context
- LTE PWS CMAS CBS消息
- 滑滑的、嫩嫩的、又一种美观的字体效果做法!
- Ubuntu 提示boot空间不足的解决办法
- LIBSVM和LIBLINEAR的优化
热门文章
- leetcode机器人运动范围Java_【LeetCode】面试题13. 机器人的运动范围
- python实现logistic_使用python实现logistic二分类
- python写日志文件_Python logging日志模块 配置文件方式
- QT之QHash简介
- JAVA子类和父类在同一个包中,子类和父类在同一个包中继承性
- f12控制台如何查看consul_基于 Consul 的 Go Micro 客户端服务发现是如何实现的
- html怎么一段时间把网页背景更换_复盛螺杆压缩机故障怎么维修?
- 如何新建分支上传_如何创建git分支?
- 将宽度赋给高度_人生应知长度、懂宽度、有高度
- sqlserver 库服务器导数据