(DT系列五)Linux kernel 是怎么将 devicetree中的内容生成plateform_device【转】
转自:https://blog.csdn.net/lichengtongxiazai/article/details/38942033
Linux kernel 是怎么将 devicetree中的内容生成plateform_device
1,实现场景(以Versatile Express V2M为例说明其过程)
以arch/arm/mach-vexpress/v2m.c 为例,在该文件中的v2m_dt_init函数的作用就是利用 dt(device tree)结构初始化 platform device。
static void __init v2m_dt_init(void)
{
of_platform_populate(NULL, of_default_bus_match_table,
v2m_dt_auxdata_lookup, NULL);
…...
}
of_platform_populate 实现在 drivers/of/platform.c,是 OF 的标准函数。调用of_platform_populate把所有的platform device加入到kernel中。
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
root = root ? of_node_get(root) : of_find_node_by_path("/");
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
}
…...
}
在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 = allnodes;
read_lock(&devtree_lock);
for (; np; np = np->allnext) {
if (np->full_name && (of_node_cmp(np->full_name, path) == 0)
&& of_node_get(np))
break;
}
read_unlock(&devtree_lock);
return np;
}
在这个函数中有一个很关键的全局变量:allnodes,它的定义是在 drivers/of/base.c 里面:struct device_node *allnodes;
这应该所就是那个所谓的“device
tree data”了。它应该指向了 device tree 的根节点。问题又来了,这个 allnodes 又是咋来的呢?我们知道
device tree 是由 DTC(Device Tree Compiler)编译成二进制文件DTB(Ddevice Tree
Blob)的,然后在系统上电之后由 bootloader 加载到内存中去,这个时候还没有device tree,而在内存中只有一个所谓的
DTB,这只是一个以某个内存地址开始的一堆原始的
dt 数据,没有树结构。kernel 的任务需要把这些数据转换成一个树结构然后再把这棵树的根节点的地址赋值给allnodes
就行了。这个过程一定是非常重要,因为没有这个 device tree 那所有的设备就没办法初始化,所以这个 dt 树的形成一定在 kernel
刚刚启动的时候就完成了。
既然如此,我们来看看 kernel 初始化的代码(init/main.c)。
2,铺垫(初始化device tree)
Kernel/init/main.c
asmlinkage void __init start_kernel(void)
{
setup_arch(&command_line);
}
这个 setup_arch 就是各个架构自己的设置函数,哪个参与了编译就调用哪个,在本文中应当是arch/arm/kernel/setup.c 中的setup_arch()。
Kernel/arch/arm/setup.c
void __init setup_arch(char **cmdline_p)
{
mdesc = setup_machine_fdt(__atags_pointer);
unflatten_device_tree();
}
这个时候 DTB 只是加载到内存中的 .dtb 文件而已,这个文件中不仅包含数据结构,还包含了一些文件头等信息,kernel 需要从这些信息中获取到数据结构相关的信息,然后再生成设备树。
struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
struct boot_param_header *devtree;
devtree = phys_to_virt(dt_phys);
initial_boot_params = devtree;
}
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,果然在随后的代码中有这么一个函数:将DTB转换成device node的结构的节点
在系统初始化的过程中,我们需要将DTB转换成节点是device_node的树状结构,以便后续方便操作。具体的代码位于setup_arch->unflatten_device_tree中。
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, &allnodes,
early_init_dt_alloc_memory_arch);
}
可以看到,allnodes 就是在这里赋值的,device tree 也是在这里正式开始建立的。
//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; ----------如果需要,删除某些属性,并挂入到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 *allnodes就是指向设备树的global list
(2)tree。
static void __unflatten_device_tree(struct boot_param_header *blob,
struct device_node **mynodes,
void * (*dt_alloc)(u64 size, u64 align))
{
//此处删除了health check代码,例如检查DTB header的magic,确认blob的确指向一个DTB。
/* scan过程分成两轮,第一轮主要是确定device-tree structure的长度,保存在size变量中 */
start = ((unsigned long)blob) +
be32_to_cpu(blob->off_dt_struct);
size = unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
size = (size | 3) + 1;
/* 初始化的时候,并不是扫描到一个node或者property就分配相应的内存,实际上内核是一次性的分配了一大片内存,这些内存包括了所有的struct device_node、node name、struct property所需要的内存。*/
mem = (unsigned long)
dt_alloc(size + 4, __alignof__(struct device_node));
((__be32 *)mem)[size / 4] = cpu_to_be32(0xdeadbeef);
/* 这是第二轮的scan,第一次scan是为了得到保存所有node和property所需要的内存size,第二次就是实打实的要构建device node tree了 */
start = ((unsigned long)blob) +
be32_to_cpu(blob->off_dt_struct);
unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
//此处略去校验溢出和校验OF_DT_END。
}
到此为止,device tree 的初始化就算完成了,在以后的启动过程中,kernel 就会依据这个 dt 来初始化各个设备。
3,具体创建platform device的过程
接着第一部分的描述:重点剖析 of_platform_bus_create()函数
of_platform_populate 实现在 drivers/of/platform.c,是 OF 的标准函数。
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
root = root ? of_node_get(root) : of_find_node_by_path("/");
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
}
…...
}
第一部分和第二部分总共完成了of_find_node_by_path("/")。这里开始分析函数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;
/* 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);//在传入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中,代码如下:
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的内存。当然,这就需要解析该device
node的interrupt资源以及memory address资源。*/
dev = of_device_alloc(np, bus_id, parent);
//设定platform_device 中的其他成员
dev->dev.coherent_dma_mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8);
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);
//把这个platform device加入统一设备模型系统中
return NULL;
}
return dev;
}
至此,Linux kernel已经完全把Device Tree中的内容生成了相对应的platform device。
(DT系列五)Linux kernel 是怎么将 devicetree中的内容生成plateform_device【转】的更多相关文章
- (DT系列五)Linux kernel 是怎么将 devicetree中的内容生成plateform_device
Linux kernel 是怎么将 devicetree中的内容生成plateform_device 1,实现场景(以Versatile Express V2M为例说明其过程)以arch/arm/ma ...
- 【转】(DT系列五)Linux kernel 是怎么将 devicetree中的内容生成plateform_device
原文网址:http://www.cnblogs.com/biglucky/p/4057495.html Linux kernel 是怎么将 devicetree中的内容生成plateform_devi ...
- 12.Linux软件安装 (一步一步学习大数据系列之 Linux)
1.如何上传安装包到服务器 有三种方式: 1.1使用图形化工具,如: filezilla 如何使用FileZilla上传和下载文件 1.2使用 sftp 工具: 在 windows下使用CRT 软件 ...
- WCF编程系列(五)元数据
WCF编程系列(五)元数据 示例一中我们使用了scvutil命令自动生成了服务的客户端代理类: svcutil http://localhost:8000/?wsdl /o:FirstServic ...
- Linux Kernel系列三:Kernel编译和链接中的linker script语法详解
先要讲讲这个问题是怎么来的.(咱们在分析一个技术的时候,先要考虑它是想解决什么问题,或者学习新知识的时候,要清楚这个知识的目的是什么). 我在编译内核的时候,发现arch/arm/kernel目录下有 ...
- Linux Kernel系列一:开篇和Kernel启动概要
前言 近期几个月将Linux Kernel的大概研究了一下,以下须要进行深入具体的分析.主要将以S3C2440的一块开发板为硬件实体.大概包含例如以下内容: 1 bootloader分析,以uboot ...
- Linux kernel的中断子系统之(五):驱动申请中断API
返回目录:<ARM-Linux中断系统>. 总结:二重点区分了抢占式内核和非抢占式内核的区别:抢占式内核可以在内核空间进行抢占,通过对中断处理进行线程化可以提高Linux内核实时性. 三介 ...
- LINUX kernel笔记系列 :IO块参数 图
Linux下,I/O处理的层次可分为4层: 系统调用层,应用程序使用系统调用指定读写哪个文件,文件偏移是多少 文件系统层,写文件时将用户态中的buffer拷贝到内核态下,并由cache缓存该部分数 ...
- Linux Kernel系列 - 黄牛X内核代码凝视
Hanks.Wang - 专注于操作系统与移动安全研究.Linux-Kernel/SELinux/SEAndroid/TrustZone/Encription/MDM Mail - byhank ...
随机推荐
- 自学Python5.1-面向对象与面向过程
自学Python之路 自学Python5.1-面向对象与面向过程 面向过程:根据业务逻辑从上到下写代码. 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机.嵌入式开 ...
- 洛谷 P4174 [NOI2006]最大获利 解题报告
P4174 [NOI2006]最大获利 题目描述 新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战.THU 集团旗下的 CS&T 通讯公司在新一代通讯技术血战的前夜,需要 ...
- ZABBIX 3.4 监控服务器TCP连接状态(六)
TCP的连接状态对于我们web服务器来说是至关重要的,尤其是并发量ESTAB:或者是syn_recv值,假如这个值比较大的话我们可以认为是不是受到了攻击,或是是time_wait值比较高的话,我们要考 ...
- 【POJ1952】逢低吸纳 dp+计数
题目大意:给定一个有 N 个数的序列,求其最长下降子序列的长度,并求出有多少种不同的最长下降子序列.(子序列各项数值相同视为同一种) update at 2019.4.3 题解:求最长下降子序列本身并 ...
- dojo.js --dojo Quick Start/dojo入门手册1
我看了http://www.cnblogs.com/mylem/archive/2009/11/11/1600984.html这篇博客以后 ,就开始设计自己的代码,其实很多解释都是在我的代码里,所以就 ...
- python爬虫 bs4_4select()教程
http://www.w3.org/TR/CSS2/selector.html 5 Selectors Contents 5.1 Pattern matching 5.2 Selector synta ...
- JavaSE学习总结(四)——Java面向对象十分钟入门
面向对象编程(Object Oriented Programming,OOP)是一种计算机模拟人类的自然思维方式的编程架构技术,解决了传统结构化开发方法中客观世界描述工具与软件结构的不一致性问题.Ja ...
- 修改tomcat启动窗口的名称
场景:我们在启动tomcat的时候,一般会出现tomcat窗口,默认窗口名字是tomcat,如果我们想知道这个tomcat的端口号,我们需要查看配置文件,很麻烦有木有.如果我们可以直接把端口号设置在窗 ...
- Hive记录-使用Hue管理Hive元数据
Hue是一个开源的Apache Hadoop UI系统,由Cloudera Desktop演化而来,最后Cloudera公司将其贡献给Apache基金会的Hadoop社区,它是基于Python Web ...
- 使用JS监听键盘按下事件(keydown event)
1.监听全局键盘按下事件,例如监听全局回车事件 1 $(document).keydown(function(event){ 2 if(event.keyCode == 13){ 3 alert('你 ...