如何理解驱动程序、设备树、platform、device、driver之间的关系
前言
利用设备树
来使用或者编写驱动程序,需要梳理哪些概念?
如何理解驱动程序、设备树、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驱动为例。
- 首先内存分配一个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函数, 、
//否则卸载时,内核找不到该函数会报错
},
};
- 分配设置一个led的驱动driver
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "cbt_led",
.of_match_table = of_match_leds,
}
};
- 编写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;
}
参考文献:
设备树 - 应用实例