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

原文网址:http://www.cnblogs.com/biglucky/p/4057495.html

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。

时间: 2024-10-09 22:58:04

【转】(DT系列五)Linux kernel 是怎么将 devicetree中的内容生成plateform_device的相关文章

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

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, o

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

前言 近期几个月将Linux Kernel的大概研究了一下,以下须要进行深入具体的分析.主要将以S3C2440的一块开发板为硬件实体.大概包含例如以下内容: 1 bootloader分析,以uboot为主,结合详细开发板的情况.我的目标是解释清楚uboot的工作原理(说实话,分析过程中不太想被硬件绑架,可是须要以一个实际的样例 来做分析) 2 kernel部分,这就非常多内容了.打算从kernel启动的流程開始分析. 3 除kernel本身外,还有非常多的知识,比如ld的输入script分析等,

Linux Kernel系列 - 牛X的内核代码注释

Hanks.Wang - 专注于操作系统与移动安全研究,Linux-Kernel/SELinux/SEAndroid/TrustZone/Encription/MDM    Mail - [email protected] 牛X的内核代码注释 大牛的代码质量高稳定性好,而且逻辑清晰易读性比较强,今天看到Linux Kernel红黑树的代码时,瞬间被大牛的代码注释秒杀了,看到这样注释的代码真的有阅读的欲望,啥也不说了,上图吧 Linux Kernel系列 - 牛X的内核代码注释

So Easy! Oracle在Linux上的安装配置系列五

So Easy! Oracle在Linux上的安装配置系列五 本篇是监听器的配置的续篇,上一小节我们创建了一个监听器,创建了密码文,在监听和实例都启动的情况下,从远程windows即时客户端连接到了oracle服务器.本篇我将继续说监听器,还将完成相关的实验 以下内容整理自网络 Oracle 监听器 Listener 是一个重要的数据库服务器组件,在整个 Oracle 体系结构中,扮演着重要的作用.它负责管理 Oracle 数据库和客户端之间的通讯,它在一个特定的网卡端口(默认是TCP 1521

Linux 5.3内核系列已终止支持 建议用户升级至Linux Kernel 5.4

上周,Linux 内核开发人员 Greg Kroah-Hartman 宣布了 Linux 5.3 内核系列的第 18 个维护更新(5.3.18).该版本共更改了 59 个文件,插入 369 项 / 移除 329 项.此外开发者指出,这将是 Linux 5.3 内核系列的最后一个维护更新.随着 Linux Kernel 5.3 抵达 EoL,官方建议用户及时更新至 Linux Kernel 5.4,以获得全面的支持与保障. Greg Kroah-Hartman 在<a href="http:

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

Hanks.Wang - 专注于操作系统与移动安全研究.Linux-Kernel/SELinux/SEAndroid/TrustZone/Encription/MDM    Mail - [email protected] 牛X的内核代码凝视 大牛的代码质量高稳定性好,并且逻辑清晰易读性比較强,今天看到Linux Kernel红黑树的代码时,瞬间被大牛的代码凝视秒杀了,看到这样凝视的代码真的有阅读的欲望.啥也不说了,上图吧 watermark/2/text/aHR0cDovL2Jsb2cuY3N

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

Linux下,I/O处理的层次可分为4层: 系统调用层,应用程序使用系统调用指定读写哪个文件,文件偏移是多少 文件系统层,写文件时将用户态中的buffer拷贝到内核态下,并由cache缓存该部分数据 块层,管理块设备I/O队列,对I/O请求进行合并.排序 设备层,通过DMA与内存直接交互,将数据写到磁盘 下图清晰地说明了Linux I/O层次结构: 写文件过程 写文件的过程包含了读的过程,文件先从磁盘载入内存,存到cache中,磁盘内容与物理内存页间建立起映射关系.用于写文件的write函数的声

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

一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的驱动的时候,如何向Linux Kernel中的中断子系统注册中断处理函数?为了理解注册中断的接口,必须了解一些中断线程化(threaded interrupt handler)的基础知识,这些在第二章描述.第三章主要描述了驱动申请 interrupt line接口API request_threaded_irq的规格.第四章是进入request_threaded_irq的实现细节,分析整个代码的执行过程. 二.和中断相关的lin

Linux Kernel - Debug Guide (Linux内核调试指南 )

http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环