如何理解驱动程序、设备树、platform、device、driver之间的关系

前言

利用设备树来使用或者编写驱动程序,需要梳理哪些概念?


一、总线设备驱动模型——总线、设备、驱动

首先要理解驱动程序模型是分离的,分层的。

  • 所谓分离是指:硬件的资源信息(地址、中断、DMA等),与软件驱动代码是分离的。硬件的资源信息是通过设备树来描述的,设备树由内核在启动时解析并注册为platform_device。

  • 所谓分层是指:分上下两层,device和driver都注册、挂载在bus(总线)下面。
    在这里插入图片描述

  • 设备device与设备树device tree相关。满足一定条件的compatible时(这些特殊的compatilbe属性为: “simple-bus”,“simple-mfd”,“isa”,"arm,amba-bus "),设备树被转换并注册成platform_device。

  • 驱动driver代码要重点理解probe函数。因为内核启动时就已经注册device,所以手动insmod是注册driver。也就是说,在利用设备树时,是先注册device,后注册driver。当driver与device的compatible相匹配时,会调用probe。

  • device与driver相匹配,是通过在drivers/base/dd.c中driver_attach函数来完成。一旦match成功,则调用driver里的probe函数。

  • 对于传统写法,match函数是直接比较name。对于使用设备树的情况下,match函数通过 platform_driver -> driver -> of_match_table -> compatile 来与设备节点做匹配。
    无设备树: 使用 xxx_driver.driver.name 进行设备匹配
    有设备树: 使用 xxx_driver.driver.of_match_table 进行设备匹配

另外,bus的注册具体参考下文:
驱动程序分层分离概念-总线设备驱动模型
linux设备驱动——总线、设备、驱动_设备和总线驱动

二、从代码中看driver与device的关系

以led驱动为例。

  1. 首先内存分配一个led设备。注意:.name与driver驱动的名字一样。
static struct platform_device led_dev = {
    .name         = "cbt_led",                           //对应的platform_driver驱动的名字
    .id       = -1,                                    //表示只有一个设备
    .num_resources    = ARRAY_SIZE(led_resource), //资源数量,ARRAY_SIZE()函数:获取数量
    .resource     = led_resource,  //资源数组led_resource
    .dev = { 
        .release = led_release,   //释放函数,必须向内核提供一个release函数, 、
                                //否则卸载时,内核找不到该函数会报错
    },
};
  1. 分配设置一个led的驱动driver
struct platform_driver led_drv = {
    .probe      = led_probe,
    .remove     = led_remove,
    .driver     = {
        .name   = "cbt_led",
        .of_match_table = of_match_leds,
    }
};
  1. 编写probe函数
  • 传统方法是:在平台drv中有这个名字(“cbt_led”),来和平台dev中的名字做匹配的,一旦匹配,则调用平台drv中的probe函数。
  • 设备树是:通过 compatile 来与设备节点做匹配。
  • 接下来编写probe函数。
static const struct of_device_id leds_of_match[] = {
    { .compatible = "cbt4412_led", .data = NULL },
    { /* sentinel */ }
};

MODULE_DEVICE_TABLE(of,leds_of_match);
 
struct platform_driver led_drv = {
    .owner = THIS_MODULE,
    .open  = led_open,
    .unlocked_ioctl = led_unlocked_ioctl,
    .release = led_release,
    .driver     = {
        .name   = "cbt_led",
        .of_match_table = leds_of_match, /* 能支持哪些来自于dts的platform_device */
    }
};

module_platform_driver(cbt4412_led_drv);

static int led_probe(struct platform_device *pdev)
{
    struct resource     *res;

    /*** 
     * platform_get_resource解析DTS
     * 根据platform_device的资源进行ioremap 
     * 参数 0代表IORESOURCE_MEM这类资源中的第0个,
     * 把他取出来后res->start,代表的就是引脚了
     * ***/
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    led_pin = res->start;

    major = register_chrdev(0, "cbt_led", &cbt_led_ops);

    led_class = class_create(THIS_MODULE, "cbt_led");
    device_create(led_class, NULL, MKDEV(major, 0), NULL, "cbt_led"); /* /dev/cbt_led */
    
    return 0;
}

参考资料:
字符设备驱动-总线设备驱动模型写法
设备树之字符设备驱动_LED

module_platform_driver的作用

module_platform_driver是宏,展开之后是驱动模块的初始化和退出函数。

module_platform_driver(gpio_led_driver);

展开之后,是

static int __init gpio_led_driver_init(void) 
{ 
	return platform_driver_register (&(gpio_led_driver)); 
} 
module_init(gpio_led_driver_init); 

static void __exit gpio_led_driver_exit(void) 
{ 
	platform_driver_unregister (&(gpio_led_driver) ); 
} 
module_exit(gpio_led_driver_exit);

参考文献
Linux 有/无设备树下 platform_driver 驱动框架

三、设备树的应用

(一)设备树的解析

dts中的节点信息终将经过dts -> dtb -> device_node -> platform_device这样的过程。但并不是所有device_node都会被转换成platform_device,需满足:

  • 根节点下含有compatile属性的子节点
  • 如果一个结点的compatile属性含有这些特殊的值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device
  • i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device

(二)设备树的解析函数

假设节点信息定义如下:

test_nod@106E0020 {
        compatible = "cbt,led";
        reg = <0x106E0020 0x4>;
        testprop,mytest;
        test_list_string = "red led", "blue led";
        interrupt-parent = <&gpv2>;
        interrupts = <1 4>;
};

1、从节点路径获取信息

struct device_node *np = NULL;
np = of_find_node_by_path("/test_nod@12345678");
printk("node name = %s\n", np->name);

2、获取到节点中的属性

    struct property *prop = NULL;
    prop = of_find_property(np, "compatible",NULL);
    printk("compatible value = %s\n", prop->value);

3、读取到属性中的整数的数组

    u32 regdata[U32_DATA_LEN];
    int ret,i=0;
    
    ret = of_property_read_u32_array(np, "reg", regdata, U32_DATA_LEN);
    printk("regdata[%d] = 0x%x\n", i,regdata[i]);

4、读取到属性中的字符串的数组

const char *pstr[3];
int i=0;
ret = of_property_read_string_index(np, "test_list_string", i, &pstr[i]);
printk("pstr[%d] = %s\n", i,pstr[i]);

5、属性的值为空,实际可以用于设置标志

if(of_find_property(np, "testprop,mytest", NULL))
{
        is_good = 1;
        printk("is_good = %d\n", is_good);
}

6、获取到中断的号码

irqno = irq_of_parse_and_map(np, 0);
printk("-----irqno = %d\n", irqno);

//验证中断号码是否有效
ret = request_irq(irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, 
            "key_irq", NULL);
if(ret)
{
    printk("request_irq error\n");
    return -EBUSY;
}

参考文献:
设备树 - 应用实例