设备树是一种描述硬件资源的数据结构,它通过bootloader将硬件资源传给内核,使得内核和硬件资源描述相对独立。
<1>DT:Device Tree //设备树
<2>FDT:Flattened Device Tree //展开设备树|开放固件,设备树起源于OF,所以我们在设备树中可以看到很多有of字母的函数
<3>device tree source(dts) //设备树代码
<4>device tree source include(dtsi)//更通用的设备树代码,也就是相同芯片但不同平台都可以使用的代码
<5>device tree blob(dtb)//DTS编译后得到的DTB文件
<6>device tree compiler(dtc)//设备树编译器
<1>设备树从根节点开始,每个设备都是一个节点。
<2>节点和节点之间可以互相嵌套,形成父子关系。
<3>设备的属性用key-value对(键值对)来描述,每个属性用分号结束
节点就好比一颗大树,从树的主干开始,然后有一节一节的树枝。根节点就相当于大树的树干
/{
};
树枝就相当于设备树的子节点
/{ //根节点
node1//子节点node1
{
};
node2//子节点2
{
};
};//分号
树枝上的树枝就相当于设备树上的子子节点
/{ //根节点
node1//子节点node1
{
child-node1//子子节点
{
};
};
node2//子节点node2
{
};
}
节点的命名有一个固定的格式
格式:<名称>[@<设备地址>]
<名称>节点的名称不是任意起的,一般要体现设备的类型,比如网口,应该命名为ethernet,
<设备地址>用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址,他主要用来区分用。
注意事项:
<1>同一级的节点只要地址不一样,名字是可以不唯一的。
<2>设备地址是一个可选选项,可以不写。但为了容易区分和理解,一般是都写的。
给一个节点取小名,相当于 typedef unsigned int uint;
节点别名格式
节点名称别名:节点名称
例:
uart8:serial@02288000
uart8就是这个节点名称的别名,serial@02288000就是节点名称。
一般往一个节点里面添加内容的时候,不会直接把添加的内容写到节点里面,而是通过节点的引用来添加。
例:
&uart8{
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart8>;
status = "okay";
}
&uart8表示引用节点别名为uart8的节点,并往这个节点添加{}里面的内容。
注意事项
编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写,使用引用可以避免移植者四处找节点。如dts和dtsi里面都有根节点,但最终会合并成一个根节点。
(1) reg属性
reg属性用来描述一个设备的地址范围。
格式:
reg=
例子:
serial@02288000{
reg = <101F2000 0x1000>;
};
其中101F2000就是起始地址,0x1000就是长度。
(2) #address-cells 和 #size-cells属性
#address-cells用来设置子节点中reg地址的数量
#size-cells 用来设置子节点中reg地址长度的数量。
举例:
cpu{
#address-cells = <1>;
#size-cells = <1>;
serial@101F2000{
compatible = "serial";
reg = <0x101F2000 0x1000
0x101f3000 0x0010>;
};
};
其中#address-cells 和#size-cells均为1,也就是说我们子节点里面的reg属性里这个寄存器组的起始地址只有一个,长度也只有一个。所以分配了2个地址范围,第一个101F2000是起始地址,0x1000是长度。第二个0x101f3000是起始地址,0x0010是长度
(3) compatible 属性
compatible 是一个字符串列表,可以在代码中进行匹配。
举例:
compatible = "led"
这个就相当于platform总线下的device.c里面编写进行匹配的属性。
(4) status 属性
status 属性的值类型是字符串,常用的一个是okay,表示设备可以正常使用,一个是disable,表示设备不能正常使用。
方法1
cd /proc/device-tree/
ls
//查看设备树的model、compatible
cat model
cat compatible
方法二
cd /sys/firmware/devicetree/base/
ls
根目录位于/home/kun/build_new/linux_kernel/arch/arm/boot/dts
/home/kun/build_new是我存放linux内核的路径
打开bcm2711-rpi-4-b.dts
找到根节点,在根节点末尾添加节点
节点代码如下
test1:test@0xfe200000{
#address-cells = <1>;
#size-cells = <1>;
compatible = "test";
reg = <0xfe200000 0x00000004>;
status = "okay";
};
在内核目录下执行指令
注:我是64位,所以ARCH=arm64,32位的话,ARCH=arm32,然后编译器我是给他重命名为了aarch64-linux-gnu- 这个编译器为编译内核时使用的编译器
make -j5 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
编译后会给出修改后的bcm2711-rpi-4-b.dtb文件位置
(/home/kun/build_new/linux_kernel/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dts)
将编译生成的bcm2711-rpi-4-b.dtb拷贝到树莓派的/boot目录下
然后reboot即可。
具体指令如下
cd /proc/device-tree
ls
cd test@0xfe200000/
ls
cat name
cat status
gpio4:gpio4@0xfe200000{
#address-cells = <1>;
#size-cells = <1>;
compatible = "test";
reg = <0xfe200000 0x00000004
0xfe20001c 0x00000004
0xfe200028 0x00000004>;
status = "okay";
};
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
unsigned int *vir_gpio4_dr=NULL;
unsigned int *vir_gpio4_h=NULL;
unsigned int *vir_gpio4_l=NULL;
u32 out_values[6]; //用于存储从设备树获取的reg数据
struct resource *gpio4_dir;
struct resource *gpio4_h;
struct resource *gpio4_l;
const struct of_device_id of_match_table_test[] = {
{ .compatible = "test_led"},
{}
};
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = {0};
if ( copy_from_user( kbuf, ubuf, size) != 0)
{
printk( "copy_from_user error\n ");
return -1;
}
printk( "kbuf is %s\n ", kbuf);
*vir_gpio4_dr |= (001<<(3*4));
if( kbuf[0] == 1)
{
*vir_gpio4_h |=(1<<4);
}
else if( kbuf[0]==0)
{
*vir_gpio4_l |=(1<<4);
}
return 0;
}
int misc_release( struct inode *inode, struct file *file)
{
printk( "hello misc_relaease bye bye \n ");
return 0;
}
int misc_open( struct inode *inode, struct file *file)
{
printk( "hello misc_open\n ");
return 0;
}
//文件操作集
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.write = misc_write,
};
//miscdevice 结构体
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc",
.fops = &misc_fops,
};
int led_probe( struct platform_device *pdev)
{
//5匹配成功后进入probe函数
int ret;
printk( "led_probe\n");
//注册杂项设备
ret = misc_register( &misc_dev);
if (ret < 0)
{
printk( "misc registe is error \n");
}
printk( "misc registe is succeed \n");
//获取设备树里面reg属性的值
ret = of_property_read_u32_array( pdev->dev.of_node, "reg", out_values, 6);
if( ret<0){
printk("of_property_read_u32_array is error \n");
return -1;
}
//对物理地址进行虚拟映射
vir_gpio4_dr = ioremap( out_values[0],out_values[1]);
if( vir_gpio4_dr== NULL )
{
printk( "gpio4dr ioremap error\n");
return EBUSY;
}
vir_gpio4_h = ioremap( out_values[2],out_values[3]);
if( vir_gpio4_h== NULL)
{
printk( "gpio4h ioremap error\n");
return EBUSY;
}
vir_gpio4_l = ioremap( out_values[4],out_values[5]);
if( vir_gpio4_l == NULL)
{
printk( "gpio4l ioremap error\n");
return EBUSY;
}
printk( "gpio ioremap success\n");
return 0;
}
int led_remove( struct platform_device *pdev)
{
printk("led_remove\n");
return 0;
}
struct platform_driver led_driver ={
//3.在led_driver结构体中完成了led_probe和led_remove
.probe = led_probe,
.remove = led_remove,
//4.在driver结构体里面填写匹配名字,让他匹配设备树里面的led_test节点
.driver = {
.owner = THIS_MODULE,
.name = "led_test", //匹配名字,匹配成功后进入probe函数
.of_match_table = of_match_table_test//优先匹配of_match_table
},
};
static int led_driver_init( void)
{
//1.看驱动文件先从init函数看
int ret = 0;
//2.在init函数里面注册了platform_driver
ret = platform_driver_register( &led_driver);
if( ret<0)
{
printk( "platform_driver_register error \n");
}
printk( "platform_driver_register ok \n");
return 0;
}
static void led_driver_exit(void)
{
misc_deregister( &misc_dev); //卸载杂项设备
printk( "misc gooodbye! \n");
iounmap( vir_gpio4_dr);
iounmap( vir_gpio4_h);
iounmap( vir_gpio4_l);
// platform 驱动卸载
platform_driver_unregister( &led_driver);
printk( "goodbye! \n");
}
module_init( led_driver_init);
module_exit( led_driver_exit);
MODULE_LICENSE( "GPL");
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int fd;
fd = open("/dev/hello_misc",O_RDWR);//打开设备节点
if(fd < 0)
{
perror("open error \n");
return fd;
}
buf[0]=atoi(argv[1]);
write(fd,buf,sizeof(buf)); //向内核层写数据
close(fd);
return 0;
}