如何理解驱动程序、设备树、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;
}

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

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用platform bus driver的方式编写Winbond SPI Flash驱动程序的示例代码,以及相应的设备树配置。 首先,创建一个名为“winbond-spi-flash”或类似的设备节点,并将其添加到设备树中。以下是一个简单的设备树配置示例: ```dts &spi0 { winbond_spi_flash: winbond-spi-flash@0 { compatible = "winbond,w25q64"; reg = <0>; spi-max-frequency = <100000000>; }; }; ``` 在驱动程序中,您需要实现probe和remove函数,用于初始化和清理设备资源。以下是一个简单的驱动程序示例: ```c #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/spi/spi.h> #define WINBOND_CMD_READ_ID 0x9f #define WINBOND_CMD_READ_DATA 0x03 static struct spi_device *winbond_spi_device; static int winbond_spi_probe(struct platform_device *pdev) { int ret; struct device *dev = &pdev->dev; u8 id[3]; printk(KERN_INFO "winbond_spi: probe\n"); // get the SPI device pointer winbond_spi_device = dev_get_drvdata(dev); // read the Winbond Flash ID struct spi_transfer transfer = { .tx_buf = &WINBOND_CMD_READ_ID, .rx_buf = id, .len = 3, }; struct spi_message message; spi_message_init(&message); spi_message_add_tail(&transfer, &message); ret = spi_sync(winbond_spi_device, &message); if (ret < 0) { printk(KERN_ERR "winbond_spi: failed to read Winbond Flash ID\n"); return ret; } printk(KERN_INFO "winbond_spi: Winbond Flash ID: %02x %02x %02x\n", id[0], id[1], id[2]); return 0; } static int winbond_spi_remove(struct platform_device *pdev) { printk(KERN_INFO "winbond_spi: remove\n"); return 0; } static struct platform_driver winbond_spi_driver = { .driver = { .name = "winbond-spi-flash", }, .probe = winbond_spi_probe, .remove = winbond_spi_remove, }; static int winbond_spi_probe(struct spi_device *spi) { return 0; } static int winbond_spi_remove(struct spi_device *spi) { return 0; } static struct spi_driver winbond_spi_driver = { .driver = { .name = "winbond-spi-flash", }, .probe = winbond_spi_probe, .remove = winbond_spi_remove, }; static int __init winbond_spi_init(void) { int ret; struct spi_master *master; struct platform_device *pdev; printk(KERN_INFO "winbond_spi: init\n"); // find the SPI master controller master = spi_busnum_to_master(0); if (!master) { printk(KERN_ERR "winbond_spi: failed to find SPI master controller\n"); return -ENODEV; } // register the SPI driver ret = spi_register_driver(&winbond_spi_driver); if (ret < 0) { printk(KERN_ERR "winbond_spi: failed to register SPI driver\n"); return ret; } // create the platform device pdev = platform_device_alloc("winbond-spi-flash", 0); if (!pdev) { printk(KERN_ERR "winbond_spi: failed to allocate platform device\n"); return -ENOMEM; } platform_device_add(pdev); return 0; } static void __exit winbond_spi_exit(void) { printk(KERN_INFO "winbond_spi: exit\n"); // remove the platform device platform_device_unregister(&winbond_spi_device->dev); // unregister the SPI driver spi_unregister_driver(&winbond_spi_driver); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Winbond SPI Flash Driver"); module_init(winbond_spi_init); module_exit(winbond_spi_exit); ``` 在该驱动程序中,probe函数用于初始化SPI总线并读取Winbond Flash的ID。在remove函数中,您可以清理和释放设备资源。 需要注意的是,驱动程序中的设备树配置和平台驱动程序名称需要匹配。在本例中,设备树中的节点名称为“winbond-spi-flash”,而平台驱动程序的名称为“winbond-spi-flash”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值