目录
一、设备树简介
二、设备树源码
三、获取设备树信息
1、增加设备节点
2、内核编译设备树
3、替换设备树文件
4、查看设备树节点
5、在驱动中获取节点的属性
6、编译驱动模块
7、加载模块
一、设备树简介
设备树的作用是描述一个硬件平台的硬件资源。这个“设备树”可以被bootloader(uboot)传递到内核,内核可以从设备树中获取硬件信息。
几个常见的文档缩写符号:
- DTS:是指.dts格式的文件,是一种ASII文本格式的设备树描述,设备树源码,一般一个.dts文件对应一个硬件平台。
- DTC:是指编译设备树源码的工具,一般情况下需要手动安装。
- DTB:是设备树源码编译生成的文件,类似于C语言中的“.bin”文件,可以被硬件识别。
二、设备树源码
文件定位:内核源码/arch/arm/boot/dts/imx6ull-mmc-npi.dts
/*----------------------头文件--------------------------*/
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/*----------------------中间部分--------------------------*/
/ {
model = "Embedfire i.MX6ULL Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
aliases {
pwm0 = &pwm1;
pwm1 = &pwm2;
pwm2 = &pwm3;
pwm3 = &pwm4;
};
chosen {
stdout-path = &uart1;
};
memory {
reg = <0x80000000 0x20000000>;
};
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x14000000>;
linux,cma-default;
};
};
/*-------------以下内容省略--------------------*/
};
/*------------------追加部分--------------------------*/
&cpu0 {
/*dc-supply = <®_gpio_dvfs>;*/
clock-frequency = <800000000>;
};
&clks {
assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <786432000>;
};
/*------------------以下内容省略--------------------------*/
文件位置:内核源码/arch/arm/boot/dts/imx6ull.dtsi
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
/*----------以下内容省略-----------------*/
};
};
设备树源码分为3部分:
- 第1-2行:头文件。头文件的引用有两种,一种为“.h”文件,一种为“.dtsi文件”。imx6ull.dtsi由NXP官方提供,是一个imx6ull平台“共用”的设备树文件。
- 中间部分:设备树节点。由{}组成,每一个设备树都只有一个“/”节点。
- 追加部分:&的意思是像原有的节点中追加内容。
三、获取设备树信息
1、增加设备节点
在ebf_linux_kernel/arch/arm/boot/dts/imx6ull-mmc-npi.dts这个设备树中,尝试增加一个新的设备节点。
led_test{
#address-cells = <1>;
#size-cells = <1>;
rgb_led_red@0x0209C000{
compatible = "fire,rgb_led_red";
reg = <0x0209C000 0x00000020>;
status = "okay";
};
};
- 以上节点中,#address-cells = <1>,#size-cells = <1>,意味着它的子节点的reg属性里的数据是“地址”、“长度”交替的。
- 第二部分led的子节点,定了三种属性,分别为compatible、reg、status。由于在父节点设置了#address-cells = <1>,#size-cells = <1>,所以0x0209C000表示的是地址(这里填写的GPIO1控制寄存器的首地址),0x00000020表示的是地址长度。
2、内核编译设备树
编译内核时会自自动编译设备树,到那时编译内核非常耗时,所以可以用以下命令只编译设备树。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfig
make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
执行结果如下:
编译成功后生成的设备树文件(.dtb)位于源码目录下/arch/arm/boot/dts/,文件名为“imx6ull-mmc-npi.dtb”
3、替换设备树文件
将上位机上生成的.dtb文件替换掉开发板中的.dtb文件,替换/usr/lib/linux-image-4.19.35-imx6/imx6ull-mmc-npi.dtb。
uboot在启动的时候负责将该目录的设备我呢见加载到内存,供内核使用。
4、查看设备树节点
设备树的设备节点在文件系统中有与之相对应的文件,位于/proc/device-tree目录,进入目录查看如下:
接着进入led_test文件夹,可以发现led_test节点中定义的属性以及它的子节点,如下所示。
在节点属性中多了一个name,我们在led节点中并没有定义name属性,这是自己生成的,保存节点名。
这里的属性是一个文件,而子系欸但是一个文件夹,再次进入“rgb_led_red@0x0209C000”文件夹。里面有compatible name regs status四个属性文件。我们可以使用“cat”命令查看这些属性文件,如下所示:
至此,成功的在设备树中添加了一个名为“led_test”的节点。
5、在驱动中获取节点的属性
实验是一个简化的字符设备驱动,在驱动中没有实际操作硬件,仅在.open函数中调用of函数获取设备树节点中的属性,获取成功后打印获取到的内容。
代码:get_dts_info.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#define DEV_NAME "get_dts_info"
#define DEV_CNT (1)
//定义字符设备的设备号
static dev_t led_devno;
//定义字符设备结构体chr_dev
static struct cdev led_chr_dev;
//创建类
struct class *led_chrdev_class;
struct device_node *led_device_node; //led的设备树节点
struct device_node *rgb_led_red_device_node; //rgb_led_red 红灯节点
struct property *rgb_led_red_property; //定义属性结构体指针
int size = 0 ;
unsigned int out_values[18]; //保存读取得到的REG 属性值
/*.open 函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
int error_status = -1;
printk("\n open form device \n");
/*获取DTS属性信息*/
led_device_node = of_find_node_by_path("/led_test");
if(led_device_node == NULL)
{
printk(KERN_ALERT "\n get led_device_node failed ! \n");
return -1;
}
/*根据 led_device_node 设备节点结构体输出节点的基本信息*/
printk(KERN_ALERT "name: %s",led_device_node->name); //输出节点名
printk(KERN_ALERT "child name: %s",led_device_node->child->name); //输出子节点的节点名
/*获取 rgb_led_red_device_node 的子节点*/
rgb_led_red_device_node = of_get_next_child(led_device_node,NULL);
if(rgb_led_red_device_node == NULL)
{
printk(KERN_ALERT "\n get rgb_led_red_device_node failed ! \n");
return -1;
}
printk(KERN_ALERT "name: %s",rgb_led_red_device_node->name); //输出节点名
printk(KERN_ALERT "parent name: %s",rgb_led_red_device_node->parent->name); //输出父节点的节点名
/*获取 rgb_led_red_device_node 节点 的"compatible" 属性 */
rgb_led_red_property = of_find_property(rgb_led_red_device_node,"compatible",&size);
if(rgb_led_red_property == NULL)
{
printk(KERN_ALERT "\n get rgb_led_red_property failed ! \n");
return -1;
}
printk(KERN_ALERT "size = : %d",size); //实际读取得到的长度
printk(KERN_ALERT "name: %s",rgb_led_red_property->name); //输出属性名
printk(KERN_ALERT "length: %d",rgb_led_red_property->length); //输出属性长度
printk(KERN_ALERT "value : %s",(char*)rgb_led_red_property->value); //属性值
/*获取 reg 地址属性*/
error_status = of_property_read_u32_array(rgb_led_red_device_node,"reg",out_values, 2);
if(error_status != 0)
{
printk(KERN_ALERT "\n get out_values failed ! \n");
return -1;
}
printk(KERN_ALERT"0x%08X ", out_values[0]);
printk(KERN_ALERT"0x%08X ", out_values[1]);
return 0;
}
/*.release 函数*/
static int led_chr_dev_release(struct inode *inode, struct file *filp)
{
printk("\nrelease\n");
return 0;
}
/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = led_chr_dev_open,
.release = led_chr_dev_release,
};
/*
*驱动初始化函数
*/
static int __init led_chrdev_init(void)
{
int ret = 0;
printk("led chrdev init\n");
//第一步
//采用动态分配的方式,获取设备编号,次设备号为0,
//设备名称为EmbedCharDev,可通过命令cat /proc/devices查看
//DEV_CNT为1,当前只申请一个设备编号
ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc led_devno\n");
goto alloc_err;
}
led_chrdev_class = class_create(THIS_MODULE, "led_chrdev");
//第二步
//关联字符设备结构体cdev与文件操作结构体file_operations
cdev_init(&led_chr_dev, &led_chr_dev_fops);
//第三步
//添加设备至cdev_map散列表中
ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
if(ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
//创建设备
device_create(led_chrdev_class, NULL, led_devno, NULL,
DEV_NAME);
return 0;
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(led_devno, DEV_CNT);
alloc_err:
return ret;
}
/*
*驱动注销函数
*/
static void __exit led_chrdev_exit(void)
{
printk("chrdev exit\n");
device_destroy(led_chrdev_class, led_devno); //清除设备
cdev_del(&led_chr_dev); //清除设备号
unregister_chrdev_region(led_devno, DEV_CNT); //取消注册字符设备
class_destroy(led_chrdev_class); //清除类
}
module_init(led_chrdev_init);
module_exit(led_chrdev_exit);
MODULE_LICENSE("GPL");
- 使用“of_find_node_by_path”函数勋章“led_test”设备节点。参数是“led_test”的设备节点路径。
- 获取成功后得到的是一个device_node类型的结构体指针,然后就可以从这个结构体中获得想要的数据。获取完整的属性可能还需要使用其他of函数。
- 获取rgb_led_red_device_node的子节点,在上一部分中获取的“led”节点的“设备节点结构体”,这里就可以使用“of_get_next_child”函数获取它的子节点。也可以从“led”节点的“设备节点结构体”中直接读取它的第一个子节点。
- 使用“of_find_property”函数获取“rgb_led_red”节点的“compatiable”属性
- 使用“of_property_read_u32_array”函数获取reg属性
6、编译驱动模块
命令:make
生成get_dts_info.ko驱动模块
7、加载模块
将ko文件拷贝到开发板,使用insmod安装模块然后可以在/dev/目录找到get_dts_info
向模块中随便输入一个字符
sudo sh -c "echo '1' >> /dev/get_dts_info"
可以看出已经成功打印出led节点属性和rgb_led_red字节点属性。