热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

嵌入式linux内核调用设备树,6.内核对设备树的处理

一、设备树的描述对于设备树,其描述的信息可以分成三部分;在内核中,对设备树的处理也会分成三部分:LinuxusesDTdat

一、设备树的描述

对于设备树,其描述的信息可以分成三部分;在内核中,对设备树的处理也会分成三部分:

Linux uses DT data for three major purposes:

1) platform identification, (平台识别信息)

2) runtime configuration, and (运行时配置信息)

3) device population. (设备信息)

二、内核head.S对dtb的简单处理

u-boot把一些参数,把设备树文件传给内核,那么内核如何处理设备树文件呢?从内核的第一个启动文件head.S着手,分析其对dtb的处理。(详细的head.s的分析可参见:嵌入式Linux完全开发手册 - 移植Linux内核 - Linux内核启动概述)

bootloader启动内核时,会设置r0,r1,r2三个寄存器,由这三个寄存器将参数传给内核:

r0一般设置为0;

r1一般设置为machine id (在使用设备树时该参数没有被使用);

r2一般设置ATAGS或DTB的开始地址;

这里额外介绍下machine id的作用。假设一个内核镜像uImage可以支持多种单板:

77665b4de9de

head.S分析如下:

a. [ __lookup_processor_type ] 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)

b. [ __vet_atags ] 判断是否存在可用的ATAGS或DTB;

c. [ __create_page_tables ] 创建页表, 即创建虚拟地址和物理地址的映射关系;

d. [ __enable_mmu ] 使能MMU, 以后就要使用虚拟地址了

e. [ __mmap_switched ] 上述函数里将会调用__mmap_switched

f. 把bootloader传入的r2参数, 保存到变量__atags_pointer中

g. 调用C函数start_kernel

head.S/head-common.S :

把bootloader传来的r1值, 赋给了C变量: __machine_arch_type

把bootloader传来的r2值, 赋给了C变量: __atags_pointer // dtb首地址

由此可见,head.S对dtb的处理还是比较简单的,只是将u-boot传来的R2寄存器里的值,也就是DTB的首地址赋给了内核中的C变量:__atags_pointer 。

三、内核对设备树中平台信息的处理(选择machine_desc)

platform identification, (平台识别信息)

上面的介绍中,已经知道在以前u-boot的ATAG传参中,会传入machine id 由此来匹配machine desc;当使用dtb传参时,u-boot并不传递machine id了,内核machine desc 的依赖于dtb 根节点下的compatible属性,内核根据该属性来找到合适的machine desc。

77665b4de9de

作为内核的使用者,我们已经列出清单,期望在内核中找到支持某个单板的machine desc 。

对于内核,每个machine desc需表明能支持哪些单板;

77665b4de9de

compatible属性对machine desc的查找

a. 设备树根节点的compatible属性列出了一系列的字符串,表示它兼容的单板名,从"最兼容"到次之。

b. 内核中有多个machine_desc, 其中有dt_compat成员, 它指向一个字符串数组, 里面表示该machine_desc支持哪些单板

c. 使用compatile属性的值, 跟每一个machine_desc.dt_compat 比较,成绩为"吻合的compatile属性值的位置",成绩越低越匹配, 对应的machine_desc即被选中

77665b4de9de

分析下代码的调用流程:

从上面的分析已经知道,内核在启动后,将dtb文件的首地址保存到变量__atags_pointer中,然后调用start_kernel,接下来从start_kernel开始分析:

函数调用过程:

start_kernel // init/main.c

setup_arch(&command_line); // arch/arm/kernel/setup.c

mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c

// 判断是否有效的dtb, drivers/of/ftd.c

early_init_dt_verify(phys_to_virt(dt_phys)

initial_boot_params = params; //将dtb的地址保存在全局变量里

// 找到最匹配的machine_desc, drivers/of/ftd.c

mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

while ((data = get_next_compat(&compat))) {

score = of_flat_dt_match(dt_root, compat);

if (score > 0 && score

best_data = data;

best_score = score;

}

}

machine_desc = mdesc;

四、内核对设备树中运行时配置信息的处理

runtime configuration, (运行时配置信息)

运行时的三种配置信息总结如下:

a. /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line

b. 确定根节点的这2个属性的值: #address-cells, #size-cells

存入全局变量: dt_root_addr_cells, dt_root_size_cells

c. 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);

函数调用过程:

start_kernel // init/main.c

setup_arch(&command_line); // arch/arm/kernel/setup.c

mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c

early_init_dt_scan_nodes(); // drivers/of/ftd.c

/* 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);

5

五、内核对设备树中设备信息的处理

device population. (设备信息)

内核启动后,会将dtb中的所有节点转换为device_node,了解了device_node结构体和properties结构体,就可知道大致情况。

➢每一个节点都转换为一个device_node结构体:

struct device_node {

const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"

const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"

phandle phandle;

const char *full_name; // 节点的名字, node-name[@unit-address]

struct fwnode_handle fwnode;

struct property *properties; // 节点的属性

struct property *deadprops; /* removed properties */

struct device_node *parent; // 节点的父亲

struct device_node *child; // 节点的孩子(子节点)

struct device_node *sibling; // 节点的兄弟(同级节点)

#if defined(CONFIG_OF_KOBJ)

struct kobject kobj;

#endif

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

};

➢device_node结构体中有properties, 用来表示该节点的属性,每一个属性对应一个property结构体:

struct property {

char *name; // 属性名字, 指向dtb文件中的字符串

int length; // 属性值的长度

void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储

struct property *next;

#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)

unsigned long _flags;

#endif

#if defined(CONFIG_OF_PROMTREE)

unsigned int unique_id;

#endif

#if defined(CONFIG_OF_KOBJ)

struct bin_attribute attr;

#endif

};

a. 在DTB文件中,

每一个节点都以TAG(FDT_BEGIN_NODE, 0x00000001)开始, 节点内部可以嵌套其他节点,

每一个属性都以TAG(FDT_PROP, 0x00000003)开始

b. 这些device_node构成一棵树, 根节点为: of_root

跟踪下代码:

函数调用过程:

start_kernel // init/main.c

setup_arch(&command_line); // arch/arm/kernel/setup.c

arm_memblock_init(mdesc); // arch/arm/kernel/setup.c

early_init_fdt_reserve_self();

/* Reserve the dtb region */

// 把DTB所占区域保留下来, 即调用: memblock_reserve

early_init_dt_reserve_memory_arch(__pa(initial_boot_params),

fdt_totalsize(initial_boot_params),

0);

early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve

unflatten_device_tree(); // arch/arm/kernel/setup.c

__unflatten_device_tree(initial_boot_params, NULL, &of_root,

early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c

/* First pass, scan for size */

size = unflatten_dt_nodes(blob, NULL, dad, NULL);

/* Allocate memory for the expanded device tree */

mem = dt_alloc(size + 4, __alignof__(struct device_node));

/* Second pass, do actual unflattening */

unflatten_dt_nodes(blob, mem, dad, mynodes);

populate_node

np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,

__alignof__(struct device_node));

np->full_name = fn = ((char *)np) + sizeof(*np);

populate_properties

pp = unflatten_dt_alloc(mem, sizeof(struct property),

__alignof__(struct property));

pp->name = (char *)pname;

pp->length = sz;

pp->value = (__be32 *)val;

对于驱动开发者,设备树中还有一个备受关注的信息:platform_device。

dts -> dtb -> device_node -> platform_device

在上述,我们已经知道dts中的节点信息终将被转换为 device_node ,但并不是所有device_node都会被转换成platform_device,需满足:

根节点下含有compatile属性的子节点

如果一个结点的compatile属性含有这些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device

i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device

转换规则:

platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;

platform_device.dev.of_node指向device_node, 可以通过它获得其他属性;

总结下:

a. 内核函数of_platform_default_populate_init, 遍历device_node树, 生成platform_device

b. 并非所有的device_node都会转换为platform_device

只有以下的device_node会转换:

b.1 该节点必须含有compatible属性

b.2 根节点的子节点(节点必须含有compatible属性)

b.3 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性):

这些特殊的compatilbe属性为: "simple-bus","simple-mfd","isa","arm,amba-bus"

示例:

比如以下的节点,

/mytest会被转换为platform_device,

因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device

/i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;

/i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。

类似的也有/spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;

/spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。

/ {

mytest {

compatile = "mytest", "simple-bus";

mytest@0 {

compatile = "mytest_0";

};

};

i2c {

compatile = "samsung,i2c";

at24c02 {

compatile = "at24c02";

};

};

spi {

compatile = "samsung,spi";

flash@0 {

compatible = "winbond,w25q32dw";

spi-max-frequency &#61; <25000000>;

reg &#61; <0>;

};

};

};

函数调用过程&#xff1a;

a. of_platform_default_populate_init (drivers/of/platform.c) 被调用到过程:

start_kernel // init/main.c

rest_init();

pid &#61; kernel_thread(kernel_init, NULL, CLONE_FS);

kernel_init

kernel_init_freeable();

do_basic_setup();

do_initcalls();

for (level &#61; 0; level

do_initcall_level(level); // 比如 do_initcall_level(3)

for (fn &#61; initcall_levels[3]; fn

do_one_initcall(initcall_from_entry(fn)); // 就是调用"arch_initcall_sync(fn)"中定义的fn函数

b. of_platform_default_populate_init (drivers/of/platform.c) 生成platform_device的过程:

of_platform_default_populate_init

of_platform_default_populate(NULL, NULL, NULL);

of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)

for_each_child_of_node(root, child) {

rc &#61; of_platform_bus_create(child, matches, lookup, parent, true); // 调用过程看下面

dev &#61; of_device_alloc(np, bus_id, parent); // 根据device_node节点的属性设置platform_device的resource

if (rc) {

of_node_put(child);

break;

}

}

c. of_platform_bus_create(bus, matches, ...)的调用过程(处理bus节点生成platform_devie, 并决定是否处理它的子节点):

dev &#61; of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 生成bus节点的platform_device结构体

if (!dev || !of_match_node(matches, bus)) // 如果bus节点的compatile属性不吻合matches成表, 就不处理它的子节点

return 0;

for_each_child_of_node(bus, child) { // 取出每一个子节点

pr_debug(" create child: %pOF\n", child);

rc &#61; of_platform_bus_create(child, matches, lookup, &dev->dev, strict); // 处理它的子节点, of_platform_bus_create是一个递归调用

if (rc) {

of_node_put(child);

break;

}

}

d. I2C总线节点的处理过程:

/i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;

platform_driver的probe函数中会调用i2c_add_numbered_adapter:

i2c_add_numbered_adapter // drivers/i2c/i2c-core-base.c

__i2c_add_numbered_adapter

i2c_register_adapter

of_i2c_register_devices(adap); // drivers/i2c/i2c-core-of.c

for_each_available_child_of_node(bus, node) {

client &#61; of_i2c_register_device(adap, node);

client &#61; i2c_new_device(adap, &info); // 设备树中的i2c子节点被转换为i2c_client

}

e. SPI总线节点的处理过程:

/spi节点一般表示spi控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;

platform_driver的probe函数中会调用spi_register_master, 即spi_register_controller:

spi_register_controller // drivers/spi/spi.c

of_register_spi_devices // drivers/spi/spi.c

for_each_available_child_of_node(ctlr->dev.of_node, nc) {

spi &#61; of_register_spi_device(ctlr, nc); // 设备树中的spi子节点被转换为spi_device

spi &#61; spi_alloc_device(ctlr);

rc &#61; of_spi_parse_dt(ctlr, spi, nc);

rc &#61; spi_add_device(spi);

}

最后&#xff0c;便是platform_device跟platform_driver的匹配过程了&#xff0c;具体的过程分析可移步&#xff1a;

1. 驱动程序分层分离概念-总线设备驱动模型。

2. 字符设备驱动-总线设备驱动模型写法。



推荐阅读
  • php缓存ri,浅析ThinkPHP缓存之快速缓存(F方法)和动态缓存(S方法)(日常整理)
    thinkPHP的F方法只能用于缓存简单数据类型,不支持有效期和缓存对象。S()缓存方法支持有效期,又称动态缓存方法。本文是小编日常整理有关thinkp ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • Java如何导入和导出Excel文件的方法和步骤详解
    本文详细介绍了在SpringBoot中使用Java导入和导出Excel文件的方法和步骤,包括添加操作Excel的依赖、自定义注解等。文章还提供了示例代码,并将代码上传至GitHub供访问。 ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 本文介绍了Java调用Windows下某些程序的方法,包括调用可执行程序和批处理命令。针对Java不支持直接调用批处理文件的问题,提供了一种将批处理文件转换为可执行文件的解决方案。介绍了使用Quick Batch File Compiler将批处理脚本编译为EXE文件,并通过Java调用可执行文件的方法。详细介绍了编译和反编译的步骤,以及调用方法的示例代码。 ... [详细]
author-avatar
陈庭勇筱玲喜芳
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有