转自: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【转】的更多相关文章

  1. (DT系列五)Linux kernel 是怎么将 devicetree中的内容生成plateform_device

    Linux kernel 是怎么将 devicetree中的内容生成plateform_device 1,实现场景(以Versatile Express V2M为例说明其过程)以arch/arm/ma ...

  2. 【转】(DT系列五)Linux kernel 是怎么将 devicetree中的内容生成plateform_device

    原文网址:http://www.cnblogs.com/biglucky/p/4057495.html Linux kernel 是怎么将 devicetree中的内容生成plateform_devi ...

  3. 12.Linux软件安装 (一步一步学习大数据系列之 Linux)

    1.如何上传安装包到服务器 有三种方式: 1.1使用图形化工具,如: filezilla 如何使用FileZilla上传和下载文件 1.2使用 sftp 工具: 在 windows下使用CRT 软件 ...

  4. WCF编程系列(五)元数据

    WCF编程系列(五)元数据   示例一中我们使用了scvutil命令自动生成了服务的客户端代理类: svcutil http://localhost:8000/?wsdl /o:FirstServic ...

  5. Linux Kernel系列三:Kernel编译和链接中的linker script语法详解

    先要讲讲这个问题是怎么来的.(咱们在分析一个技术的时候,先要考虑它是想解决什么问题,或者学习新知识的时候,要清楚这个知识的目的是什么). 我在编译内核的时候,发现arch/arm/kernel目录下有 ...

  6. Linux Kernel系列一:开篇和Kernel启动概要

    前言 近期几个月将Linux Kernel的大概研究了一下,以下须要进行深入具体的分析.主要将以S3C2440的一块开发板为硬件实体.大概包含例如以下内容: 1 bootloader分析,以uboot ...

  7. Linux kernel的中断子系统之(五):驱动申请中断API

    返回目录:<ARM-Linux中断系统>. 总结:二重点区分了抢占式内核和非抢占式内核的区别:抢占式内核可以在内核空间进行抢占,通过对中断处理进行线程化可以提高Linux内核实时性. 三介 ...

  8. LINUX kernel笔记系列 :IO块参数 图

      Linux下,I/O处理的层次可分为4层: 系统调用层,应用程序使用系统调用指定读写哪个文件,文件偏移是多少 文件系统层,写文件时将用户态中的buffer拷贝到内核态下,并由cache缓存该部分数 ...

  9. Linux Kernel系列 - 黄牛X内核代码凝视

    Hanks.Wang - 专注于操作系统与移动安全研究.Linux-Kernel/SELinux/SEAndroid/TrustZone/Encription/MDM    Mail - byhank ...

随机推荐

  1. 自学Aruba5.2-Aruba安全认证-有PEFNG 许可证环境的角色策略管理

    点击返回:自学Aruba之路 自学Aruba5.2-Aruba安全认证- 有PEFNG 许可证环境的角色策略管理 导入许可后,可以对Role进行配置: 1. 系统自带的Role的可以修改的属性: 2. ...

  2. 51nod1236 序列求和 V3 【数学】

    题目链接 51nod1236 题解 用特征方程求得斐波那契通项: \[f(n) = \frac{(\frac{1 + \sqrt{5}}{2})^{n} - (\frac{1 - \sqrt{5}}{ ...

  3. (转) JVM——Java类加载机制总结

    背景:对java类的加载机制,一直都是模糊的理解,这篇文章看下来清晰易懂. 转载:http://blog.csdn.net/seu_calvin/article/details/52301541 1. ...

  4. 爬虫acm比赛成绩(多页成绩整合在一起、获取复制不了的数据)(hihocoder、计蒜客)

    https://github.com/congmingyige/web-crawler_rank-of-competition-in-JiSuanKe-and-hihocoder 1. 计蒜客(获取复 ...

  5. memcache、redis原理对比

    一.问题:     数据库表数据量极大(千万条),要求让服务器更加快速地响应用户的需求.   二.解决方案:      1.通过高速服务器Cache缓存数据库数据      2.内存数据库     ( ...

  6. 安装使用hibernate tools

    help-Eclipse marketplace-hibernate(搜索)-jboss tools(安装)

  7. Linux上安装Perl模块的两种方法

    Linux/Unix下安装Perl模块有两种方法:手工安装和自动安装.第一种方法是从CPAN上下载  您需要的模块,手工编译.安装.第二种方法是联上internet,使用一个叫做CPAN的模块自动完 ...

  8. centos7环境下在线安装mysql

    卸载mariadb centos默认安装了mariadb,因此,在安装mysql之前,需要卸载系统中安装的mariadb. 查看系统中所有已安装的mariadb包.命令:rpm -qa | grep ...

  9. mybatis中Parameter index out of range (1 > number of parameters, which is 0).

    Parameter index out of range (1 > number of parameters, which is 0).(参数索引超出范围) 在mybatis里面写就是应该是 l ...

  10. android measure的时候报空指针

    1.使用listview的时候,在代码中动态设置其高度,在android低版本中,这个低版本是以4.4为界,会报measure的空指针,原因是低版本relativelayout有个bug,使用list ...