嵌入式Linux驱动笔记(五)------学习platform设备驱动

来源:互联网 发布:免费的经济数据库软件 编辑:程序博客网 时间:2024/05/17 21:53

你好!这里是风筝的博客,

欢迎和我一起交流。



设备是设备,驱动是驱动。

如果把两个糅合写一起,当设备发生变化时,势必要改写整个文件,这是非常愚蠢的做法。如果把他们分开来,当设备发生变化时,只要改写设备文件即可,驱动文件巍然不动。

从linux2.6内核起,引入一套新的驱动管理和注册机制:platform_device 和 platform_driver 。Linux 中大部分的设备驱动,都可以使用这套机制,设备用 platform_device 表示;驱动用 platform_driver 进行注册。

platform将驱动分为platform_device (设备文件)和platform_driver(驱动文件),他们会通过platform总线来相配对。当设备注册到总线时,会通过总线去寻找有没有相对应的驱动文件,有的话则将他两配对。同理,当驱动注册到总线时,会通过总线去寻找有没有相对应的设备文件,有的话也将他两进行配对。

linux platform driver 机制和传统的device driver机制(即:通过 driver_register 函数进行注册)相比,一个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中用使用这些资源时,通过platform device提供的标准接口进行申请并使用。

 

kernel 4.8.17为例,驱动文件:

  1. platform_driver_register(&led_drv);  
  2.     ——>__platform_driver_register  
  3.         ——>drv->driver.bus = &platform_bus_type;  
  4.             ——>.match      = platform_match,  
  5.                 ——>of_driver_match_device(dev, drv)  
  6.                     ——>of_match_device(drv->of_match_table, dev)  
  7.                         ——>of_match_node(matches, dev->of_node)  
  8.                             ——>__of_match_node(matches, node)  
  9.                                 ——>__of_device_is_compatible(node, matches->compatible,matches->type, matches->name)  
  10.   
  11.                 ——>acpi_driver_match_device(dev, drv)  
  12.                 ——>platform_match_id(pdrv->id_table, pdev)  
  13.                 ——>strcmp(pdev->name, drv->name)  

代码如上,驱动注册时,会在总线上与设备匹配,有四种匹配方法:

1)如5行,通过这个OpenFirmware的匹配方式,匹配name、type、和compatible字符串三个属性,三者要同时相同(一般name、和type为空,只比较compatible字符串),compatible这个好像是在设备树(dts)里说到,这个之后再讨论。如果不匹配,则会进行第二种匹配方式。

2)如11行,我也不知道这个acpi_driver_match_device是什么,反正也是如果不匹配,则会进行第三种匹配方式。

3)如12行,通过id_table方式匹配,比较设备的名字和id_table里的名字是否有相同的。这样在id_table可以实现一个驱动对应多个设备。如果没有,则会进行第四种匹配方式了。

4)如13行,直接比较设备名字和驱动名字。

即使匹配不成功,也会driver_register(&drv->driver)进行注册,等带设备注册时来与驱动匹配。

如果匹配成功,则会引发驱动的probe()函数执行。


设备文件:

  1. platform_device_register(&led_dev)    
  2.     platform_device_add(pdev)    
  3.         pdev->dev.bus = &platform_bus_type    
  4.             .match      = platform_match    
  5.                 /*之后就一样了*/  


那么他们具体是怎么操作的呢?我们来具体分析,以platform_device_register为例

int platform_device_register(struct platform_device *pdev){device_initialize(&pdev->dev);arch_setup_pdev_archdata(pdev);return platform_device_add(pdev);}

这里面,先初始化device,其中涉及kset,可以看看这篇文章:嵌入式Linux驱动学习笔记(十六)------设备驱动模型(kobject、kset、ktype)

然后是platform_device_add函数:

int platform_device_add(struct platform_device *pdev){int i, ret;if (!pdev)return -EINVAL;if (!pdev->dev.parent)pdev->dev.parent = &platform_bus;pdev->dev.bus = &platform_bus_type;switch (pdev->id) {default:dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);break;case PLATFORM_DEVID_NONE:dev_set_name(&pdev->dev, "%s", pdev->name);break;case PLATFORM_DEVID_AUTO:ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);if (ret < 0)goto err_out;pdev->id = ret;pdev->id_auto = true;dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);break;}for (i = 0; i < pdev->num_resources; i++) {struct resource *p, *r = &pdev->resource[i];if (r->name == NULL)r->name = dev_name(&pdev->dev);p = r->parent;if (!p) {if (resource_type(r) == IORESOURCE_MEM)p = &iomem_resource;else if (resource_type(r) == IORESOURCE_IO)p = &ioport_resource;}if (p && insert_resource(p, r)) {dev_err(&pdev->dev, "failed to claim resource %d\n", i);ret = -EBUSY;goto failed;}}pr_debug("Registering platform device '%s'. Parent at %s\n", dev_name(&pdev->dev), dev_name(pdev->dev.parent));ret = device_add(&pdev->dev);if (ret == 0)return ret;/*省略部分代码*/}

这里面,这是了所属总线,填充好名字,就会调用device_add函数了,

这个函数也很复杂,我放在嵌入式Linux驱动笔记(十六)------设备驱动模型(kobject、kset、ktype)这里讲了

但是,复杂的那些细节我们我们先可以不看,我们看到device_add函数里调用bus_probe_device函数,这一个探测函数:

void bus_probe_device(struct device *dev){struct bus_type *bus = dev->bus;struct subsys_interface *sif;if (!bus)return;if (bus->p->drivers_autoprobe)//设置了自动匹配初始化那么就开始匹配 device_initial_probe(dev);mutex_lock(&bus->p->mutex);list_for_each_entry(sif, &bus->p->interfaces, node)if (sif->add_dev)sif->add_dev(dev, sif);mutex_unlock(&bus->p->mutex);}

这里面,调用了device_initial_probe函数,device_initial_probe又调用了__device_attach函数,我们继续看看:

static int __device_attach(struct device *dev, bool allow_async){int ret = 0;device_lock(dev);if (dev->driver) {if (device_is_bound(dev)) {ret = 1;goto out_unlock;}ret = device_bind_driver(dev);if (ret == 0)ret = 1;else {dev->driver = NULL;ret = 0;}} else {struct device_attach_data data = {.dev = dev,.check_async = allow_async,.want_async = false,};if (dev->parent)pm_runtime_get_sync(dev->parent);ret = bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver);if (!ret && allow_async && data.have_async) {dev_dbg(dev, "scheduling asynchronous probe\n");get_device(dev);async_schedule(__device_attach_async_helper, dev);} else {pm_request_idle(dev);}if (dev->parent)pm_runtime_put(dev->parent);}out_unlock:device_unlock(dev);return ret;}

函数一开始,先检查device是否绑定过了,接着调用device_bind_driver对device和driver进行绑定:

int device_bind_driver(struct device *dev){int ret;ret = driver_sysfs_add(dev);//将driver和dev使用link,链接到一起,使他们真正相关 if (!ret)driver_bound(dev);//将私有成员的driver节点挂到了driver的设备链表 else if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,     BUS_NOTIFY_DRIVER_NOT_BOUND, dev);//通知bus上所有设备bound消息 return ret;}

static int driver_sysfs_add(struct device *dev){int ret;if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,     BUS_NOTIFY_BIND_DRIVER, dev);ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,  kobject_name(&dev->kobj));//驱动目录下dev->kobj目录链接到dev->kobj if (ret == 0) {ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,"driver");//在dev->kobj目录下的driver目录链接到其驱动目录if (ret)sysfs_remove_link(&dev->driver->p->kobj,kobject_name(&dev->kobj));}return ret;}

接着调用__device_attach_driver进行match:

static int __device_attach_driver(struct device_driver *drv, void *_data){struct device_attach_data *data = _data;struct device *dev = data->dev;bool async_allowed;int ret;/* * Check if device has already been claimed. This may * happen with driver loading, device discovery/registration, * and deferred probe processing happens all at once with * multiple threads. */if (dev->driver)return -EBUSY;ret = driver_match_device(drv, dev);if (ret == 0) {/* no match */return 0;} else if (ret == -EPROBE_DEFER) {dev_dbg(dev, "Device match requests probe deferral\n");driver_deferred_probe_add(dev);} else if (ret < 0) {dev_dbg(dev, "Bus failed to match device: %d", ret);return ret;} /* ret > 0 means positive match */async_allowed = driver_allows_async_probing(drv);if (async_allowed)data->have_async = true;if (data->check_async && async_allowed != data->want_async)return 0;return driver_probe_device(drv, dev);}

这里面,先调用driver_match_device进行各种关键字match:

static inline int driver_match_device(struct device_driver *drv,      struct device *dev){return drv->bus->match ? drv->bus->match(dev, drv) : 1;}


然后就是调用driver_probe_device函数触发probe函数:

int driver_probe_device(struct device_driver *drv, struct device *dev){int ret = 0;if (!device_is_registered(dev))return -ENODEV;pr_debug("bus: '%s': %s: matched device %s with driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name);if (dev->parent)pm_runtime_get_sync(dev->parent);pm_runtime_barrier(dev);ret = really_probe(dev, drv);//调用really_probe pm_request_idle(dev);if (dev->parent)pm_runtime_put(dev->parent);return ret;}

函数一开始检查device是否注册过,如果注册过,直接return。

否则,则调用really_probe函数:

static int really_probe(struct device *dev, struct device_driver *drv){int ret = -EPROBE_DEFER;int local_trigger_count = atomic_read(&deferred_trigger_count);if (defer_all_probes) {/* * Value of defer_all_probes can be set only by * device_defer_all_probes_enable() which, in turn, will call * wait_for_device_probe() right after that to avoid any races. */dev_dbg(dev, "Driver %s force probe deferral\n", drv->name);driver_deferred_probe_add(dev);return ret;}atomic_inc(&probe_count);pr_debug("bus: '%s': %s: probing driver %s with device %s\n", drv->bus->name, __func__, drv->name, dev_name(dev));WARN_ON(!list_empty(&dev->devres_head));dev->driver = drv;/* If using pinctrl, bind pins now before probing */ret = pinctrl_bind_pins(dev);if (ret)goto pinctrl_bind_failed;if (driver_sysfs_add(dev)) {//驱动目录下建立一个到设备的同名链接,并且在设备目录下建立一个名为 driver.到驱动的链接printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",__func__, dev_name(dev));goto probe_failed;}if (dev->pm_domain && dev->pm_domain->activate) {ret = dev->pm_domain->activate(dev);if (ret)goto probe_failed;}/* * Ensure devices are listed in devices_kset in correct order * It's important to move Dev to the end of devices_kset before * calling .probe, because it could be recursive and parent Dev * should always go first */devices_kset_move_last(dev);if (dev->bus->probe) {ret = dev->bus->probe(dev);//如果bus的probe存在就用bus的if (ret)goto probe_failed;} else if (drv->probe) {//如果bus的不存在driver的存在ret = drv->probe(dev);//再用driver的if (ret)goto probe_failed;}pinctrl_init_done(dev);if (dev->pm_domain && dev->pm_domain->sync)dev->pm_domain->sync(dev);driver_bound(dev);//调用driver_bound进行绑定ret = 1;pr_debug("bus: '%s': %s: bound device %s to driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name);goto done;/*省略部分代码*/}

这里,先调用driver_sysfs_add函数,把drivers添加到sysfs中:

static int driver_sysfs_add(struct device *dev){int ret;if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,     BUS_NOTIFY_BIND_DRIVER, dev);ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,  kobject_name(&dev->kobj));//驱动目录下dev->kobj目录链接到dev->kobj if (ret == 0) {ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,"driver");//在dev->kobj目录下的driver目录链接到其驱动目录if (ret)sysfs_remove_link(&dev->driver->p->kobj,kobject_name(&dev->kobj));}return ret;}

最后,也是我们期望看到的,probe函数的触发:

if (dev->bus->probe) {ret = dev->bus->probe(dev);//如果bus的probe存在就用bus的if (ret)goto probe_failed;} else if (drv->probe) {//如果bus的不存在driver的存在ret = drv->probe(dev);//再用driver的if (ret)goto probe_failed;}


我们的platform总线是不自带probe的,所以这里对触发drv->probe,好了,分析到这里,就大功告成了!

platform_driver_register函数也是一样的分析方法,就不多累述了。


所以我们主要还是 构造好这platform_driver个驱动结构体,结构体原型为:

  1. struct platform_driver {    
  2.     int (*probe)(struct platform_device *);/*匹配成功之后调用该函数*/     
  3.     int (*remove)(struct platform_device *);  /*卸载了调用该函数*/  
  4.     void (*shutdown)(struct platform_device *);    
  5.     int (*suspend)(struct platform_device *, pm_message_t state);    
  6.     int (*resume)(struct platform_device *);    
  7.     struct device_driver driver;  /*内核里所有的驱动程序必须包含该结构体*/  
  8.     const struct platform_device_id *id_table;    
  9.     bool prevent_deferred_probe;    
  10. };   


设备的结构体为:

  1. struct platform_device {    
  2.     const char  *name;  /*名字*/  
  3.     int     id;    
  4.     bool        id_auto;    
  5.     struct device   dev;  /*硬件模块必须包含该结构体*/  
  6.     u32     num_resources;  /*资源个数*/  
  7.     struct resource *resource;  /*资源*/  
  8.     
  9.     const struct platform_device_id *id_entry;    
  10.     char *driver_override; /* Driver name to force a match */    
  11.     
  12.     /* MFD cell pointer */    
  13.     struct mfd_cell *mfd_cell;    
  14.     
  15.     /* arch specific additions */    
  16.     struct pdev_archdata    archdata;    
  17. };   

其中,有个重要的参数:resource(资源),结构体如下 :

  1. struct resource {    
  2.     resource_size_t start;/*资源的起始地址*/    
  3.     resource_size_t end;/*资源的结束地址*/    
  4.     const char *name;/*资源的名字*/    
  5.     unsigned long flags;/*资源的类型*/    
  6.     unsigned long desc;    
  7.     struct resource *parent, *sibling, *child;    
  8. };  

flags类型的可选参数有:

IORESOURCE_TYPE_BITS

IORESOURCE_IO/*IO地址空间*/

IORESOURCE_MEM/*属于外设或者用于和设备通讯的支持直接寻址的地址空间*/

IORESOURCE_REG/*寄存器偏移*/

IORESOURCE_IRQ

IORESOURCE_DMA

IORESOURCE_BUS

start、end的含义会随着flags而变更,如:

当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;

当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。

对于同种类型的资源而言,可以有多份,譬如说某设备占据了2个内存区域,则可以定义2个IORESOURCE_MEM资源。

下面给出完整程序参考,摘抄自韦东山驱动视频。

驱动文件:

  1. /* 分配/设置/注册一个platform_driver */  
  2. #include <linux/module.h>  
  3. #include <linux/version.h>  
  4.   
  5. #include <linux/init.h>  
  6. #include <linux/fs.h>  
  7. #include <linux/interrupt.h>  
  8. #include <linux/irq.h>  
  9. #include <linux/sched.h>  
  10. #include <linux/pm.h>  
  11. #include <linux/sysctl.h>  
  12. #include <linux/proc_fs.h>  
  13. #include <linux/delay.h>  
  14. #include <linux/platform_device.h>  
  15. #include <linux/input.h>  
  16. #include <linux/irq.h>  
  17. #include <asm/uaccess.h>  
  18. #include <asm/io.h>  
  19.   
  20. static int major;  
  21. static struct class *cls;  
  22. static volatile unsigned long *gpio_con;  
  23. static volatile unsigned long *gpio_dat;  
  24. static int pin;  
  25.   
  26. static int led_open(struct inode *inode, struct file *file)  
  27. {  
  28.     /* 配置为输出 */  
  29.     *gpio_con &= ~(0x3<<(pin*2));  
  30.     *gpio_con |= (0x1<<(pin*2));  
  31.     return 0;     
  32. }  
  33.   
  34. static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)  
  35. {  
  36.     int val;  
  37.   
  38.     copy_from_user(&val, buf, count); //    copy_to_user();  
  39.   
  40.     if (val == 1)  
  41.     {  
  42.         // 点灯  
  43.         *gpio_dat &= ~(1<<pin);  
  44.     }  
  45.     else  
  46.     {  
  47.         // 灭灯  
  48.         *gpio_dat |= (1<<pin);  
  49.     }  
  50.       
  51.     return 0;  
  52. }  
  53.   
  54. static struct file_operations led_fops = {  
  55.     .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */  
  56.     .open   =   led_open,       
  57.     .write  =   led_write,       
  58. };  
  59.   
  60. static int led_probe(struct platform_device *pdev)  
  61. {  
  62.     struct resource     *res;  
  63.   
  64.     /* 根据platform_device的资源进行ioremap */  
  65.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  66.     gpio_con = ioremap(res->start, res->end - res->start + 1);  
  67.     gpio_dat = gpio_con + 1;  
  68.   
  69.     res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  
  70.     pin = res->start;  
  71.   
  72.     /* 注册字符设备驱动程序 */  
  73.   
  74.     printk("led_probe, found led\n");  
  75.   
  76.     major = register_chrdev(0, "myled", &led_fops);  
  77.   
  78.     cls = class_create(THIS_MODULE, "myled");  
  79.   
  80.     //class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */  
  81.     device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */  
  82.       
  83.     return 0;  
  84. }  
  85.   
  86. static int led_remove(struct platform_device *pdev)  
  87. {  
  88.     /* 卸载字符设备驱动程序 */  
  89.     /* iounmap */  
  90.     printk("led_remove, remove led\n");  
  91.   
  92.     //class_device_destroy(cls, MKDEV(major, 0));  
  93.     device_destroy(cls, MKDEV(major, 0));  
  94.     class_destroy(cls);  
  95.     unregister_chrdev(major, "myled");  
  96.     iounmap(gpio_con);  
  97.       
  98.     return 0;  
  99. }  
  100.   
  101. struct platform_driver led_drv = {  
  102.     .probe      = led_probe,  
  103.     .remove     = led_remove,  
  104.     .driver     = {  
  105.         .name   = "myled",  
  106.     }  
  107. };  
  108.   
  109. static int led_drv_init(void)  
  110. {  
  111.     platform_driver_register(&led_drv);  
  112.     return 0;  
  113. }  
  114.   
  115. static void led_drv_exit(void)  
  116. {  
  117.     platform_driver_unregister(&led_drv);  
  118. }  
  119.   
  120. module_init(led_drv_init);  
  121. module_exit(led_drv_exit);  
  122.   
  123. MODULE_LICENSE("GPL");  

设备文件:

  1. /* 分配/设置/注册一个platform_device */  
  2. #include <linux/module.h>  
  3. #include <linux/version.h>  
  4.   
  5. #include <linux/init.h>  
  6.   
  7. #include <linux/kernel.h>  
  8. #include <linux/types.h>  
  9. #include <linux/interrupt.h>  
  10. #include <linux/list.h>  
  11. #include <linux/timer.h>  
  12. #include <linux/init.h>  
  13. #include <linux/serial_core.h>  
  14. #include <linux/platform_device.h>  
  15.   
  16. static struct resource led_resource[] = {  
  17.     [0] = {  
  18.         .start = 0x56000050,  
  19.         .end   = 0x56000050 + 8 - 1,  
  20.         .flags = IORESOURCE_MEM,/*指的是属于外设或者用于和设备通讯的支持直接寻址的地址空间*/  
  21.     },  
  22.     [1] = {  
  23.         .start = 5,  
  24.         .end   = 5,  
  25.         .flags = IORESOURCE_IRQ,  
  26.     }  
  27. };  
  28.   
  29. static void led_release(struct device * dev)  
  30. {  
  31. }  
  32.   
  33. static struct platform_device led_dev = {  
  34.     .name               = "myled",  
  35.     .id                 = -1,  
  36.     .num_resources      = ARRAY_SIZE(led_resource),/*资源个数*/  
  37.     .resource           = led_resource,  
  38.     .dev                = {   
  39.                             .release = led_release,   
  40.                     },  
  41. };  
  42.   
  43. static int led_dev_init(void)  
  44. {  
  45.     platform_device_register(&led_dev);  
  46.     return 0;  
  47. }  
  48.   
  49. static void led_dev_exit(void)  
  50. {  
  51.     platform_device_unregister(&led_dev);  
  52. }  
  53.   
  54. module_init(led_dev_init);  
  55. module_exit(led_dev_exit);  
  56.   
  57. MODULE_LICENSE("GPL");  

需要注意的是:platform_driver 和 platform_device 中的 name 变量的值必须是相同的 。这样在 platform_driver_register() 注册时,会将当前注册的 platform_driver 中的 name 变量的值和已注册的所有 platform_device 中的 name 变量的值进行比较,只有找到具有相同名称的 platform_device 才能注册成功。当注册成功时,会调用 platform_driver 结构元素 probe 函数指针,运行.probe进行初始化。


最后,结合十六节文章分析口味更佳!

嵌入式Linux驱动学习笔记(十六)------设备驱动模型(kobject、kset、ktype)