在对总线设备驱动进行详细说明时可以看出,虽然总线设备驱动可以实现驱动和设备分离,但是总线设备驱动引发另外的一个问题就是在相同的芯片不同的开发板上,当外设资源不同时需要在不同的设备文件中去定义引脚,这样就导致开发板中保留大量设备文件。为了解决这一问题引入了设备树。设备树是为内核中的驱动程序定义硬件信息。上层应用层调用相关设备文件时,会去调用底层的相关设备驱动文件,而驱动所需要操作的相关引脚是由设备树对其进行制定。
在单板上电启动时,首先运行的是bootloader,bootloader的主要作用是启动时为内核传递参数,bootloader会将设备树文件和内核读入到内存中,bootloader会将设备树的地址给到内核。
查看设备树文件:
首先需要编译内核,
先设备环境变量:
在内核目录下编译设备树文件:
进入到/arch/arm/boot/dts文件夹下,找到对应的设备树文件,通过使用dtc命令反编译设备树文件,使用dtc编译后就可以将dts内容中包含的一些信息展开:
-I表示输入文件 -O表示输出文件格式
在编译出来的1.dts中可以查看全部的设备树节点信息:
在树莓派3b对应的设备树文件中添加设备树节点信息:
在Linux内核源码目录下使用make dtbs命令进行编译,生成bcm2710-rpi-3-b.dtb文件,将该文件赋值到树莓派boot目录中,重新启动树莓派,让bootloader给内核传递设备树信息。
未加载新的dtb信息之前查看设备树节点信息:
加载新的dtb信息之后查看设备树节点信息:
在设备树中如果对设备节点添加compatible属性就可以让该设备树节点成为platform_device,查看总线设备如下:
打印设备树相关信息可以看到
其中name是自动添加与设备lable后面的名字一样。
含有compatible属性的设备树节点如何找到对应的设备驱动,Linux内核源码如下:
首先找到platform_bus_type中的match函数中用于设备树匹配的那一项
根据函数中的参数,找到对应platform_driver结构体中的device_driver中的of_match_table这一项
查看对应of_device_id结构体的原型,可以看到其中有compatible
在整个内核源码中查找of-match_table找到相应的代码示例
因此就可以知道驱动代码中需要再platform_driver中的driver选项中添加of_match_table选项,并在结构体外定义该结构体,其中包含该平台驱动可以包含的设备树相应的compatible选项。
根据上述分析可以编写平台驱动代码进行测试:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LED_MAX_CNT 10
struct led_desc {
int pin;
int minor;
};
//确定主设备号
static int major = 0;
static struct class *led_class;
static int g_ledcnt = 0;
static struct led_desc leds_desc[LED_MAX_CNT];
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
struct inode *inode = file_inode(file);
int minor = iminor(inode);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
printk("set led pin 0x%x as %d\n", leds_desc[minor].pin, status);
return 1;
}
static int led_drv_open (struct inode *node, struct file *file)
{
int minor = iminor(node);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
printk("init led pin 0x%x as output\n", leds_desc[minor].pin);
return 0;
}
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.write = led_drv_write,
};
//实现platform_driver的probe函数
static int led_probe(struct platform_device *pdev)
{
int minor;
int i = 0;
struct resource *res;
const char *tmp_str;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
if(!pdev->dev.of_node){ //普通的平台设备
res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
if (!res)
return -EINVAL;
minor = g_ledcnt;
leds_desc[minor].pin = res->start; //获取引脚信息
}else{ //设备树设备
of_property_read_string(pdev->dev.of_node,"pin",&tmp_str); //获取设备树相关信息到tmp_str中
printk("pin = %s\n",tmp_str);
minor = g_ledcnt;
leds_desc[minor].pin = tmp_str[6] - '0'; //通过设备树中设置的pin信息获取引脚信息
}
leds_desc[minor].minor = minor; //次设备号
device_create(led_class, NULL, MKDEV(major, minor), NULL, "led_driver%d", minor);
platform_set_drvdata(pdev, &leds_desc[minor]);
g_ledcnt++;
return 0;
}
static int led_remove(struct platform_device *pdev)
{
struct led_desc *led = platform_get_drvdata(pdev);
device_destroy(led_class, MKDEV(major, led->minor));
return 0;
}
static const struct of_device_id device_ids[] = { //用于与设备树compatible进行匹配的信息
{ .compatible = "CAOHAI" }, //与设备树中的compatible进行匹配
{ /* sentinel */ }
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "led_driver",
.of_match_table = device_ids, //添加与of_match_table作为匹配选项
},
};
static int __init led_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "led_driver", &led_drv);
led_class = class_create(THIS_MODULE, "led_driver_class");
err = PTR_ERR(led_class);
if (IS_ERR(led_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "led");
return -1;
}
//注册platform_driver
err = platform_driver_register(&led_driver);
return err;
}
static void __exit led_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&led_driver);
class_destroy(led_class);
unregister_chrdev(major, "led_driver");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
在probe函数中可以看到以下相关信息:
编译完驱动代码,将代码传送至树莓派装载,可以进入到sys虚拟文件系统中查看总线驱动相关信息如下:
可以看到在sys虚拟文件系统中该驱动与设备树中的led_for_test_ok相匹配。使用dmesg打印底层相关信息
learned from:韦东山