百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Linux驱动 | 从0写一个设备树节点实例

haoteby 2025-02-16 20:56 9 浏览

一、前言

设备树是每一个Linux驱动工程师都必须掌握的一个知识点,有很多之前做单片机的朋友刚接触Linux驱动时,会一脸懵!

其实设备树的使用并没有大家想像的那么复杂,对于大部分工程师来说,只要会修改即可。

很多粉丝留言说,希望彭老师提供一个设备树到驱动解析的实例。

必须安排!

在学习设备树之前,大家一定要搞清楚什么是platform总线,请详细学习下面这篇文章:

《手把手教Linux驱动10-platform总线详解》

关于设备树理论部分内容请学习下面这篇文章:

《手把手教linux驱动11-linux设备驱动统一模型》

关于驱动基础文章,可以去B站学习一口君的入门视频:

《从学Linux驱动入门视频》

https://www.bilibili.com/video/BV1d5411A7VJ?spm_id_from=333.999.0.0

有了这些基础知识后,我们就可以来编写一个设备树的实例,

下面彭老师就给大家讲解如何自己添加一个设备树节点,并如何在驱动中提取出设备树的信息。

老规矩,代码从0开始编写,并且全部验证通过,并分享给大家。

二、测试平台

本次测试在开发板上操作,操作环境如下:

1. 编译环境

ubuntu 16.04

2. 交叉编译工具

root@ubuntu:/home/peng/linux-3.14# arm-none-linux-gnueabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-none-linux-gnueabi-gcc
COLLECT_LTO_WRAPPER=/home/peng/toolchain/gcc-4.6.4/bin/../libexec/gcc/arm-arm1176jzfssf-linux-gnueabi/4.6.4/lto-wrapper
Target: arm-arm1176jzfssf-linux-gnueabi
………………
gcc version 4.6.4 (crosstool-NG hg+default-2685dfa9de14 - tc0002)

3. 开发板

开发板:fs4412
soc:exynos4412

4. 内核版本

Linux kernel 3.14.0

三、内核解析设备树一般过程

  1. 系统启动后,uboot会从网络或者flash、sd卡中读取设备树文件(具体由uboot命令给出),
  2. 引导linux内核启动后,会把设备树镜像保存到的内存地址传递给Linux内核,Linux内核会解析设备树镜像,从设备树中提取硬件信息并逐一初始化。
  3. 其中设备树信息会被转换成struct platform_device类型变量。
  4. 而驱动要解析设备树,必须定义 struct platform_driver类型结构体变量,并通过函数platform_driver_register()注册。
  5. 这两者都会注册到platform总线,当驱动和设备树节点匹配成功后,就调用 struct platform_driver.probe方法。

其中设备树节点会封装在struct device_node结构体变量中 各个属性信息会封装在 struct property结构体变量中, 他们与struct platform_device结构体之间关系如下:

四、驱动架构

以下是一口君编写的驱动架构,

我们只需要将测试代码填充到hello_probe()中即可:

static int hello_probe(struct platform_device *pdev)
{
 printk("match ok \n");
 
//解析代码编写
 return 0;
}
static  int hello_remove(struct platform_device *pdev)
{
 printk("hello_remove \n");
 return 0;
}
static struct of_device_id beep_table[] = {
  {.compatible = "yikoulinux"},
};
static struct platform_driver hello_driver =
{
 .probe = hello_probe,
 .driver.name = "duang",
 .remove = hello_remove,
 .driver = {
  .name = "yikoupeng",
  .of_match_table = beep_table,
 },
};
static int hello_init(void)
{
 printk("hello_init \n");
 return platform_driver_register(&hello_driver);
}
static void hello_exit(void)
{
 printk("hello_exit \n");
 platform_driver_unregister(&hello_driver);
 return;
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

五、设备树节点

下面是给出的设备树信息:

 yikou_node{
        compatible = "yikoulinux";
        reg = <0x114000a0 0x4 0x139D0000 0x20>;
        reg-names = "peng";
        interrupt-parent=<&gpx1>;
        interrupts =<1 2>,<2  2>;
    
        csm_gpios=<&gpx2 3 0 &gpx2 4 0 &gpx2 5 0 &gpx2 6 0>;
        
        crl0_gpio=<&gpx0 5 0>;
        crl1_gpio=<&gpx0 6 0>;
        rst_gpio=<&gpx0 7 0>;
        cfg_gpio=<&gpx0 4 0>;
  
  phy_ref_freq = <26000>;  /* kHz */  
  suspend_poweroff;
  
  clock-names = "xusbxti",
   "otg";
  yikou_node {
   compatible = "leadcore,dsi-panel";
   panel_name = "lcd_rd_rm67295";
   refresh_en = <1>;
   bits-per-pixel = <32>; 
  };
    };

其中包括常见reg、中断、整型值、bool值、字符串、子节点、时钟等属性。

一定要注意,很多属性的给出会因为使用的SOC平台的不同有所差异, 下面介绍下GPIO和中断编写原理:

1. GPIO

gpio信息的给出有以下两种方法:

  csm_gpios=<&gpx2 3 0 &gpx2 4 0 &gpx2 5 0 &gpx2 6 0>;
 crl0_gpio=<&gpx0 5 0>;
 crl1_gpio=<&gpx0 6 0>;
 rst_gpio=<&gpx0 7 0>;
 cfg_gpio=<&gpx0 4 0>;

第1种是公用同一个名字,第2种是每一个gpio单独使用1个名字。

gpio需要指明父节点,关于gpio父节点的说明下说明文档(通常linux-3.14\Documentation下有关于该内核版本的一些模块说明,很重要):

linux-3.14\Documentation\devicetree\bindings\gpio.txt
For example, the following could be used to describe gpios pins to use
as chip select lines; with chip selects 0, 1 and 3 populated, and chip
select 2 left empty:

 gpio1: gpio1 {
  gpio-controller
   #gpio-cells = <2>;
 };
 gpio2: gpio2 {
  gpio-controller
   #gpio-cells = <1>;
 };
 [...]
  chipsel-gpios = <&gpio1 12 0>,
    <&gpio1 13 0>,
    <0>, /* holes are permitted, means no GPIO 2 */
    <&gpio2 2>;
Note that gpio-specifier length is controller dependent.  In the
above example, &gpio1 uses 2 cells to specify a gpio, while &gpio2
only uses one.

gpio-specifier may encode: bank, pin position inside the bank,
whether pin is open-drain and whether pin is logically inverted.
Exact meaning of each specifier cell is controller specific, and must
be documented in the device tree binding for the device.

Example of the node using GPIOs:

 node {
  gpios = <&qe_pio_e 18 0>;
 };

In this example gpio-specifier is "18 0" and encodes GPIO pin number,
and empty GPIO flags as accepted by the "qe_pio_e" gpio-controller.

翻译总结成如下几点:

  1. gpio父节点需要包含属性
 gpio-controller、    表示是gpi控制器
 #gpio-cells = <2>;    表示子节点包括2个属性
  1. 对于子节点是2个属性的函数 比如:
 gpios = <&qe_pio_e 18 0>;

父节点是qe_pio_e 其中18表示GPIO pin值,就是gpio下面管理的pin脚序号,该pin值一般就需要查询用户手册&电路图。

2. 中断

中断属性节点如下:

        interrupt-parent=<&gpx1>;
        interrupts =<1 2>,<2  2>;

其中

interrupt-parent=<&gpx1>;: 该中断信号所述的中断控制器
interrupts =<1 2>,<2  2>;  :描述中断属性,其中<>中第一个值表示该中断所述中断控制器index,第二个值表示中断触发方式

中断子节点格式如下:

linux-3.14\Documentation\devicetree\bindings\gpio.txt
Example of a peripheral using the GPIO module as an IRQ controller:

 funkyfpga@0 {
  compatible = "funky-fpga";
  ...
  interrupt-parent = <&gpio1>;   #父节点
  interrupts = <4 3>;     #节点属性
 };

中断子节点说明文档如下:

linux-3.14\Documentation\devicetree\bindings\interrupt-controller\interrupts.txt
  b) two cells
  ------------
  The #interrupt-cells property is set to 2 and the first cell defines the
  index of the interrupt within the controller, while the second cell is used
  to specify any of the following flags:
    - bits[3:0] trigger type and level flags
        1 = low-to-high edge triggered          上升沿
        2 = high-to-low edge triggered    下降沿
        4 = active high level-sensitive      高电平有效
        8 = active low level-sensitive          低电平有效

我们所填写的中断父节点gpx1定义如下(该文件由三星厂家出厂定制好):

linux-3.14\arch\arm\boot\dts\exynos4x12-pinctrl.dtsi
  gpx1: gpx1 {
   gpio-controller;        #gpio控制器
   #gpio-cells = <2>;      #子节点有2个属性

   interrupt-controller;  #中断控制器
   interrupt-parent = <&gic>;    #父节点gic
   interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,   #子节点属性约束
         <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
   #interrupt-cells = <2>;
  };

可见三星的exynos4412平台中gpx1,既可以做gpio控制器又可以做中断控制器,而gpx1作为中断控制器则路由到gic上。 其中interrupts属性说明如下:

linux-3.14\Documentation\devicetree\bindings\arm\gic.txt
Main node required properties:

- compatible : should be one of:
 "arm,gic-400"
 "arm,cortex-a15-gic"
 "arm,cortex-a9-gic"
 "arm,cortex-a7-gic"
 "arm,arm11mp-gic"
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
  interrupt source.  The type shall be a  and the value shall be 3.

  The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
  interrupts.

  The 2nd cell contains the interrupt number for the interrupt type.
  SPI interrupts are in the range [0-987].  PPI interrupts are in the
  range [0-15].

  The 3rd cell is the flags, encoded as follows:
 bits[3:0] trigger type and level flags.
  1 = low-to-high edge triggered
  2 = high-to-low edge triggered
  4 = active high level-sensitive
  8 = active low level-sensitive
 bits[15:8] PPI interrupt cpu mask.  Each bit corresponds to each of
 the 8 possible cpus attached to the GIC.  A bit set to '1' indicated
 the interrupt is wired to that CPU.  Only valid for PPI interrupts.

翻译总结:

interrupts = <0 24 0>
  1. 第1个0 表示该中断是SPI类型中断,如果是1表示PPI类型中断
  2. 24表示中断号(通过查询电路图和datasheet获得)
  3. 第三个0表示中断触发方式

再强调一遍 不同的平台gpio、中断控制器管理可能不一样,所以填写方法可能会有差异,不可教条

六、驱动提取设备树信息方法

驱动解析代码与设备树节点之间关系如下,代码与属性用相同颜色框出:

of开头的函数请参考《手把手教linux驱动11-linux设备驱动统一模型》

七、编译(ubuntu中操作)

驱动编译:

注意,内核必须提前编译好

设备树编译:

编译设备树命令,各个厂家的SDK都不尽相同,本例制作参考。

除此之外驱动模块文件、设备树文件如何导入给开发板,差别也比较大,本文不再给出步骤。

八、加载模块(开发板上操作)

加载模块后执行结果如下:

[root@peng test]# insmod driver.ko 
[   26.880000] hello_init 
[   26.880000] match ok 
[   26.880000] mem_res1 : [0x114000a0]  mem_res2:[0x139d0000] 
[   26.885000] irq_res1 : [168]  irq_res2:[169] 
[   26.890000] mem_resp:[114000a0]
[   26.890000] 
[   26.895000] phy_ref_freq:26000
[   26.900000] suspend_poweroff [true]
[   26.900000] suspend_poweroff_test [false]
[   26.900000] 
[   26.905000] csm_gpios :[231][232][233][234]
[   26.910000] CTL0:[217] CTL1:[218] RST:[219] CFG:[216]
[   26.915000] bits_per_pixel:32
[   26.920000] panel_name:lcd_rd_rm67295
[   26.925000] refresh_en [true]

其中打印的信息就是最终我们解析出的设备树里的硬件信息, 我们就可以根据这些信息进行相关资源申请、初始化。

同时设备树中的信息,会以文件节点形式创建在以下目录中:

相关推荐

Python爬虫进阶教程(二):线程、协程

简介线程线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能...

A320-V2500发动机系统FADEC介绍(2)

目的全权数字发动机控制(FADEC)系统在所有飞行和运行阶段提供全范围发动机控制。...

三国志战棋版:玩家“二叔”用这套群DOT在比武中拿下31胜5负

声明:本文首发于今日头条,而后发布于“鼎叔闯三棋”的微信公众号、抖音、哔哩哔哩和小红书平台,如果在其他平台就是抄袭。...

真正的独一无二:Dot One 推出 DNA 定制系列 139英镑起

相信很多人在挑选衣物时有着这样的困扰,综合了性价比、面料等因素后好不容易找到了心仪的款式,还要担心是否会撞衫,不管是擦肩而过的陌生人还是身边的熟人,都令人尴尬。小部分人为此热衷于购买少量的古着或者限量...

崩铁:周年庆福利再升级,老角色加强时间确定,3.xdot体系反转

#埃安UT大一圈高级很多#...

Dotgo推出RBMHub,扩大了CPaaS提供商的覆盖范围和功能

据telecompaper网7月15日报道,用于商业消息传递的RichCommunicationServices(RCS)解决方案的领先提供商Dotgo宣布推出RBMHub。RBMHub的推出扩大了C...

深度解析:快照取消Dot职业的将何去何从

写在前面曾几何时,术士的出现便被冠以dot大师的名头,从远古时期的献祭腐蚀虹吸不如暗牧一个痛,到TBC上满dot=荣誉击杀+1,到wlk接近全暴击的冰晶腐蚀,再到CTM就算了吧MOP的各种变态吸x放...

星穹铁道:抽卡芙卡之前,你必须了解什么是dot!

卡妈终于上线了,可还是有很多人不明白什么是dot伤害,抽了卡妈直接玩起了直伤流,把一个持续伤害的引爆器玩成了打手,卡妈打dot伤害是远高于直伤的,有了卡妈的玩家一直了解dot,不然这卡妈就真被玩成四不...

游戏界的闪耀星辰陨落:悼念知名游戏博主″dotα牛娃″

无尽哀思!在数字时代浪潮中,游戏不仅是消遣娱乐的代名词,更是连接心灵的桥梁,构筑了无数人的青春回忆。在这片浩瀚无垠的游戏宇宙中,有这样一位博主,他以独特的风采、深邃的洞察力和无尽的热情,成为了玩家心中...

直击2017新加坡同性恋聚会Pink Dot,自由爱!

今年的“粉红点”又来啦~这个支持LGBT群体(男女同志、双性恋、跨性别等)群体的活动,从2009年起,已经在新加坡举办8年了!”这个非营利的同性恋权益活动,主要是希望大家了解到,不管一个人的性倾向或...

python-dotenv,一款超级实用处理环境变量python库

python-dotenv,一款超级实用处理环境变量python库python-dotenv概述:...

亚马逊语音助手毫无征兆发笑 诡异至极吓坏用户

来源:新华网美国电商亚马逊7日承诺,将更改名下“亚历克萨”语音系统设置,令它不会莫名发笑,免得吓坏用户。“亚历克萨”是亚马逊开发的语音助手软件,可服从用户语音指令完成对话、播放音乐等任务。依照原来设计...

2022最火英文网名男女生

精选好听英文昵称带翻译1.moveon(离开)2.Monster(怪物)3.Solo吉他手4.Finish.(散场)...

智能家具 RecycleDot 的出现给传统家具厂商带来新的挑战

从可穿戴手环、手表到智能衣服,智能硬件逐步渗透到每一个领域。最近有一对父子MikeSandru和JohnSandru在自家的车库中设计了一款智能家具RecycleDot,给日渐萧条的家具行...

欧洲通信卫星公司 OneWeb 敦促印度DoT尽早批准提供卫星宽带服务

据telecomtalk2月17日报道,欧洲通信卫星公司EutelsatOneWeb近日敦促印度电信部(DoT)尽快批准其在印度部署双地球站网关的计划,以便连接其近地轨道(LEO)全球卫星星座,并...