Linux驱动 device 的probe函数是怎么被调用的

本文详细解析了Linux平台设备model中platform device的probe函数调用流程,从设备注册到驱动匹配,再到probe函数的具体调用过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天正好有空,研究了一下platformdevice的probe函数时如何被调用的。我觉得这个过程应该可以推广到一般设备的探测函数的调用。

以mini2440中的watchdog为例。

先看配置文件中对watchdog的设置:

[cpp]  view plain  copy
 print ?
  1. static struct resource s3c_wdt_resource[] = {  
  2.     [0] = {  
  3.         .start = S3C24XX_PA_WATCHDOG,  
  4.         .end   = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,  
  5.         .flags = IORESOURCE_MEM,  
  6.     },  
  7.     [1] = {  
  8.         .start = IRQ_WDT,  
  9.         .end   = IRQ_WDT,  
  10.         .flags = IORESOURCE_IRQ,  
  11.     }  
  12.   
  13. };  
  14.   
  15. struct platform_device s3c_device_wdt = {  
  16.     .name         = "s3c2410-wdt",  
  17.     .id       = -1,  
  18.     .num_resources    = ARRAY_SIZE(s3c_wdt_resource),  
  19.     .resource     = s3c_wdt_resource,  
  20. };  


 

在系统启动时 mini2440_machine_init调用了 platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices)); 其中 mini2440_devices 中就包括了s3c_deice_wdt。

[cpp]  view plain  copy
 print ?
  1. int platform_add_devices(struct platform_device **devs, int num)  
  2. {  
  3.     int i, ret = 0;  
  4.   
  5.     for (i = 0; i < num; i++) {  
  6.         ret = platform_device_register(devs[i]);  
  7.         if (ret) {  
  8.             while (--i >= 0)  
  9.                 platform_device_unregister(devs[i]);  
  10.             break;  
  11.         }  
  12.     }  
  13.   
  14.     return ret;  
  15. }  
[cpp]  view plain  copy
 print ?
  1. int platform_device_register(struct platform_device *pdev)  
  2. {  
  3.     device_initialize(&pdev->dev);  
  4.     return platform_device_add(pdev);  
  5. }  
[cpp]  view plain  copy
 print ?
  1. int platform_device_add(struct platform_device *pdev)  
  2. {  
  3.     int i, ret = 0;  
  4.   
  5.     if (!pdev)  
  6.         return -EINVAL;  
  7.   
  8.     if (!pdev->dev.parent)  
  9.         pdev->dev.parent = &platform_bus;  
  10.   
  11.     pdev->dev.bus = &platform_bus_type;  
  12.   
  13.     if (pdev->id != -1)  
  14.         dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);  
  15.     else  
  16.         dev_set_name(&pdev->dev, "%s", pdev->name);  
  17.   
  18.     for (i = 0; i < pdev->num_resources; i++) {  
  19.         struct resource *p, *r = &pdev->resource[i];  
  20.   
  21.         if (r->name == NULL)  
  22.             r->name = dev_name(&pdev->dev);  
  23.   
  24.         p = r->parent;  
  25.         if (!p) {  
  26.             if (resource_type(r) == IORESOURCE_MEM)  
  27.                 p = &iomem_resource;  
  28.             else if (resource_type(r) == IORESOURCE_IO)  
  29.                 p = &ioport_resource;  
  30.         }  
  31.   
  32.         if (p && insert_resource(p, r)) {  
  33.             printk(KERN_ERR  
  34.                    "%s: failed to claim resource %d\n",  
  35.                    dev_name(&pdev->dev), i);  
  36.             ret = -EBUSY;  
  37.             goto failed;  
  38.         }  
  39.     }  
  40.   
  41.     pr_debug("Registering platform device '%s'. Parent at %s\n",  
  42.          dev_name(&pdev->dev), dev_name(pdev->dev.parent));  
  43.   
  44.     <span style="color:#ff0000">ret = device_add(&pdev->dev);  
  45. </span> if (ret == 0)  
  46.         return ret;  
  47.   
  48.  failed:  
  49.     while (--i >= 0) {  
  50.         struct resource *r = &pdev->resource[i];  
  51.         unsigned long type = resource_type(r);  
  52.   
  53.         if (type == IORESOURCE_MEM || type == IORESOURCE_IO)  
  54.             release_resource(r);  
  55.     }  
  56.   
  57.     return ret;  
  58. }  



内核把设备挂在虚拟的platform bus下面。platformbus 虽说在物理上不存在这样的bus,但是在内核的代码中,platform bus是有真是的代码的,和其他的bus并没有区别。上面的device_add就是将设备加入到系统中。这个函数时非常重要的,这里不列出代码了。

    到现在为止,仅仅是设备加入到了系统中,设备还没有与驱动联系到一起。下面分析驱动的加载过程,就可以看到驱动是怎么样和设备关联到一起的。

    驱动部分:

[cpp]  view plain  copy
 print ?
  1. static struct platform_driver s3c2410wdt_driver = {  
  2.     .probe      = s3c2410wdt_probe,  
  3.     .remove     = __devexit_p(s3c2410wdt_remove),  
  4.     .shutdown   = s3c2410wdt_shutdown,  
  5.     .suspend    = s3c2410wdt_suspend,  
  6.     .resume     = s3c2410wdt_resume,  
  7.     .driver     = {  
  8.         .owner  = THIS_MODULE,  
  9.         .name   = "s3c2410-wdt",  
  10.     },  
  11. };  
  12.   
  13.   
  14. static char banner[] __initdata =  
  15.     KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";  
  16.   
  17. static int __init watchdog_init(void)  
  18. {  
  19.     printk(banner);  
  20.     return platform_driver_register(&s3c2410wdt_driver);  
  21. }  
  22.   
  23. static void __exit watchdog_exit(void)  
  24. {  
  25.     platform_driver_unregister(&s3c2410wdt_driver);  
  26. }  

 

[cpp]  view plain  copy
 print ?
  1. int platform_driver_register(struct platform_driver *drv)  
  2. {  
  3.     drv->driver.bus = &platform_bus_type;  
  4.     if (drv->probe)  
  5.         drv->driver.probe = platform_drv_probe;  
  6.     if (drv->remove)  
  7.         drv->driver.remove = platform_drv_remove;  
  8.     if (drv->shutdown)  
  9.         drv->driver.shutdown = platform_drv_shutdown;  
  10.   
  11.     return<span style="color:#cc0000"> driver_register(&drv->driver);  
  12. </span>}  

上面这个过程有点像是C++中的虚函数。看下面的这两个结构体:

[cpp]  view plain  copy
 print ?
  1. struct <span style="color:#ff6600">platform_driver</span> {  
  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.     struct platform_device_id *id_table;  
  9. };  
[cpp]  view plain  copy
 print ?
  1. struct <span style="color:#ff6600">device_driver</span> {  
  2.     const char      *name;  
  3.     struct bus_type     *bus;  
  4.   
  5.     struct module       *owner;  
  6.     const char      *mod_name;  /* used for built-in modules */  
  7.   
  8.     bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */  
  9.   
  10.     int (*probe) (struct device *dev);  
  11.     int (*remove) (struct device *dev);  
  12.     void (*shutdown) (struct device *dev);  
  13.     int (*suspend) (struct device *dev, pm_message_t state);  
  14.     int (*resume) (struct device *dev);  
  15.     const struct attribute_group **groups;  
  16.   
  17.     const struct dev_pm_ops *pm;  
  18.   
  19.     struct driver_private *p;  
  20. };  

在platform_device 和 device_driver 中都包含了 函数指针: probe  remove  shutdown  suspend resume 等。device_driver是基类,platform_driver是派生类。这里具体的面向对象的思想我还没有琢磨明白。到现在这些函数指针都已经进行了赋值。下面看一下 device_driver的probe函数:

[cpp]  view plain  copy
 print ?
  1. static int platform_drv_probe(struct device *_dev)  
  2. {  
  3.     struct platform_driver *drv = to_platform_driver(_dev->driver);  
  4.     struct platform_device *dev = to_platform_device(_dev);  
  5.   
  6.     return drv->probe(dev);  
  7. }  

这里相当于是用手动的方法 去调用了派生类的probe函数,相当于是手动实现了C++中的虚函数。这样就会调用到s3c2410wdt_driver的probe函数。

好,接下来我们看platform_drv_probe函数是如何实现调用的。

[cpp]  view plain  copy
 print ?
  1. int driver_register(struct device_driver *drv)  
  2. {  
  3.     int ret;  
  4.     struct device_driver *other;  
  5.   
  6.     BUG_ON(!drv->bus->p);  
  7.   
  8.     if ((drv->bus->probe && drv->probe) ||  
  9.         (drv->bus->remove && drv->remove) ||  
  10.         (drv->bus->shutdown && drv->shutdown))  
  11.         printk(KERN_WARNING "Driver '%s' needs updating - please use "  
  12.             "bus_type methods\n", drv->name);  
  13.   
  14.     other = driver_find(drv->name, drv->bus);  
  15.     if (other) {  
  16.         put_driver(other);  
  17.         printk(KERN_ERR "Error: Driver '%s' is already registered, "  
  18.             "aborting...\n", drv->name);  
  19.         return -EBUSY;  
  20.     }  
  21.   
  22.     ret = <span style="color:#cc0000">bus_add_driver</span>(drv);  
  23.     if (ret)  
  24.         return ret;  
  25.     ret = driver_add_groups(drv, drv->groups);  
  26.     if (ret)  
  27.         bus_remove_driver(drv);  
  28.     return ret;  
  29. }  
[cpp]  view plain  copy
 print ?
  1. int bus_add_driver(struct device_driver *drv)  
  2. {  
  3.     struct bus_type *bus;  
  4.     struct driver_private *priv;  
  5.     int error = 0;  
  6.   
  7.     bus = bus_get(drv->bus);  
  8.     if (!bus)  
  9.         return -EINVAL;  
  10.   
  11.     pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);  
  12.   
  13.     priv = kzalloc(sizeof(*priv), GFP_KERNEL);  
  14.     if (!priv) {  
  15.         error = -ENOMEM;  
  16.         goto out_put_bus;  
  17.     }  
  18.     klist_init(&priv->klist_devices, NULL, NULL);  
  19.     priv->driver = drv;  
  20.     drv->p = priv;  
  21.     priv->kobj.kset = bus->p->drivers_kset;  
  22.     error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,  
  23.                      "%s", drv->name);  
  24.     if (error)  
  25.         goto out_unregister;  
  26.   
  27.     if (drv->bus->p->drivers_autoprobe) {  
  28.         <span style="color:#ff0000">error = driver_attach(drv);</span>  
  29.         if (error)  
  30.             goto out_unregister;  
  31.     }  
  32.     klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);  
  33.     module_add_driver(drv->owner, drv);  
  34.   
  35.     error = driver_create_file(drv, &driver_attr_uevent);  
  36.     .....  
  37.     error = driver_add_attrs(bus, drv);  
  38.       
  39.     return error;  
  40. }  


 

[cpp]  view plain  copy
 print ?
  1. int driver_attach(struct device_driver *drv)  
  2. {  
  3.     return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);  
  4. }  


其中bus_for_each_dev就是对已经添加到drv->bus上面的每个设备,这行_driver_attach函数。

[cpp]  view plain  copy
 print ?
  1. static int __driver_attach(struct device *dev, void *data)  
  2. {  
  3.     struct device_driver *drv = data;  
  4.   
  5.     if (!<span style="color:#cc0000">driver_match_device</span>(drv, dev))  
  6.         return 0;  
  7.   
  8.     if (dev->parent) /* Needed for USB */  
  9.         down(&dev->parent->sem);  
  10.     down(&dev->sem);  
  11.     if (!dev->driver)  
  12.         <span style="color:#cc0000">driver_probe_device</span>(drv, dev);  
  13.     up(&dev->sem);  
  14.     if (dev->parent)  
  15.         up(&dev->parent->sem);  
  16.   
  17.     return 0;  
  18. }  

上面就是我们要看的地方,其中driver_match_device是检测设备和驱动是否匹配,drvier_probe_device 就是执行探测函数。下面分别看这两个函数。

[cpp]  view plain  copy
 print ?
  1. static inline int driver_match_device(struct device_driver *drv,  
  2.           struct device *dev)  
  3. {  
  4.  return drv->bus->match ? drv->bus->match(dev, drv) : 1;  
  5. }  
[cpp]  view plain  copy
 print ?
  1. static int platform_match(struct device *dev, struct device_driver *drv)  
  2. {  
  3.     struct platform_device *pdev = to_platform_device(dev);  
  4.     struct platform_driver *pdrv = to_platform_driver(drv);  
  5.   
  6.     /* match against the id table first */  
  7.     if (pdrv->id_table)  
  8.         return platform_match_id(pdrv->id_table, pdev) != NULL;  
  9.   
  10.     /* fall-back to driver name match */  
  11.     return (strcmp(pdev->name, drv->name) == 0);  
  12. }  
[cpp]  view plain  copy
 print ?
  1. struct bus_type platform_bus_type = {  
  2.     .name       = "platform",  
  3.     .dev_attrs  = platform_dev_attrs,  
  4.     .match      = platform_match,  
  5.     .uevent     = platform_uevent,  
  6.     .pm     = &platform_dev_pm_ops,  
  7. };  

 

其实,bus_type中的match函数也就是platform_match函数,才是真正的将驱动和设备进行匹配的函数。如果成功就返回0. 看到了其中比较了 pdev->name  和 drv->name 。具体到watchdog的驱动中, 这两个对应的就是字符串:"s3c2410-wdt"。以前一直以为是在probe函数中匹配device和driver。现在可以看到其实是在bus_type的match函数中进行的匹配。

在driver_probe_device中又调用了really_probe,我们直接看这个函数:

[cpp]  view plain  copy
 print ?
  1. static int really_probe(struct device *dev, struct device_driver *drv)  
  2. {  
  3.     int ret = 0;  
  4.   
  5.     atomic_inc(&probe_count);  
  6.     dev->driver = drv;  
  7.     if (driver_sysfs_add(dev)) {  
  8.         printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",  
  9.             __func__, dev_name(dev));  
  10.         goto probe_failed;  
  11.     }  
  12.   
  13.     if (dev->bus->probe) {  
  14.         ret = <span style="color:#000000">dev->bus->probe(dev);</span>  
  15.         if (ret)  
  16.             goto probe_failed;  
  17.     } else if (drv->probe) {  
  18.         ret = <span style="color:#cc0000">drv->probe(dev);</span>  
  19.         if (ret)  
  20.             goto probe_failed;  
  21.     }  
  22.   
  23.     driver_bound(dev);  
  24.     ret = 1;  
  25.     pr_debug("bus: '%s': %s: bound device %s to driver %s\n",  
  26.          drv->bus->name, __func__, dev_name(dev), drv->name);  
  27.     goto done;  
[cpp]  view plain  copy
 print ?
  1. }  


就是在这个函数中,通过drv->probe从而调用了platform_drv_probe函数。其实上面的函数中 dev->bus->probe 和 drv->probe应该指向的是相同的函数。

到这里,我们的脉络已经清晰了。

<think>好的,我现在要回答用户关于Linux驱动开发中probe函数的作用与实现的问题。首先,我需要回忆自己之前学过的驱动开发知识,并结合用户提供的引用内容来组织答案。 用户提到了几个引用,其中引用[1]和[3]解释了probe函数何时被调用,即当设备与驱动匹配时,总线会调用驱动probe函数。引用[5]详细说明了probe函数的结构和功能,比如初始化设备、分配资源等。引用[2]和[4]提到了在probe函数中注册设备节点以及使用cdev_init等函数。这些信息需要整合到回答中。 首先,probe函数的作用是什么?根据引用[1]和[5],probe函数是在设备被检测到时由内核调用的,用于初始化设备、分配资源、设置寄存器等。这应该是作用部分的核心内容。 然后是probe函数的实现流程。根据引用[5]中的示例,probe函数通常会进行设备初始化,检查硬件是否存在,分配资源,注册设备节点,设置操作函数等步骤。同时,引用[2]和[4]提到注册设备节点和使用cdev_init,这些步骤需要在实现流程中详细说明。 接下来,需要讨论probe调用时机,这里引用[1]、[3]指出当设备和驱动匹配时,总线(如platform总线)会调用probe函数。可能还需要提到设备树匹配或ACPI匹配的情况,但用户提供的引用中没有这些,可能需要避免推测,只根据现有引用说明。 另外,用户提供的代码片段如cdev_init和文件操作函数的结构,应该在实现流程中涉及,比如初始化字符设备和注册文件操作函数。引用[4]中的cdev_init是用来初始化cdev结构体的,并关联file_operations,这应该在资源初始化部分提到。 需要注意的是,用户可能对probe函数参数(如client或id)的来源有疑问,但根据引用[1],用户提到参数不是驱动定义的,而是由系统传递的。这部分可能需要解释参数的意义,比如struct platform_device *pdev代表对应的设备信息,驱动可以通过它获取资源或设备树数据。 最后,引用[5]中的示例代码结构可以作为一个例子,说明probe函数通常包含错误处理步骤,比如初始化失败时返回错误码。 现在需要将以上信息组织成结构清晰的回答,分点说明作用、实现流程、调用时机,并附上示例代码。同时,生成相关问题需要考虑用户可能进一步想了解的内容,比如probe函数和init函数的区别,如何调试probe函数,或者匹配机制的具体细节。 检查是否有遗漏的引用内容,比如引用[3]提到probe调用前的流程,这可能需要在调用时机部分简要提及,但用户问题主要关注probe本身,所以可能不需要深入流程细节。 确保使用中文回答,行内公式正确使用$...$,独立公式用$$...$$,但这个问题可能不涉及数学公式,所以可以忽略。代码块需要用正确的语法标注,比如C代码和可能的Kernel函数。 现在开始组织内容: 作用部分: - 设备初始化(硬件检查、寄存器配置) - 资源分配(内存、中断) - 注册设备节点(引用[2]中的内容) - 关联操作函数(file_operations,引用[4]的cdev_init) 实现流程: 1. 参数解析(从platform_device获取资源) 2. 硬件检查 3. 资源分配 4. 初始化设备结构体(如cdev_init) 5. 注册设备(如cdev_add) 6. 文件操作函数设置 调用时机: - 设备与驱动匹配时(总线触发,引用[1]和[3]) 示例代码结构: 如引用[5]中的代码,展示probe函数的基本结构,包括初始化步骤和错误处理。 相关问题可能涉及: 1. probe函数何时被调用? 2. 如何获取设备资源(如设备树中的数据)? 3. probe函数出错如何处理? 4. 与init函数的区别? 5. 如何调试probe函数中的问题? 需要确保每个引用在回答中被正确引用,例如引用[1]用于调用时机,引用[5]用于作用,引用[2][4]用于实现步骤。 最后检查是否所有用户提供的引用都被合理利用,没有遗漏关键点,同时回答结构清晰,分点明确。</think>### Linux驱动开发:probe函数的作用与实现 #### 一、probe函数的作用 1. **设备初始化** probe函数负责完成设备的硬件初始化,例如配置寄存器、检查设备是否存在等。这是驱动与设备建立联系的第一步[^5]。 2. **资源分配** 包括申请内存、中断、DMA等硬件资源。例如,通过`request_mem_region`或`devm_kmalloc`分配内存。 3. **注册设备节点** 在probe函数调用`cdev_init`初始化字符设备,并通过`cdev_add`将其注册到内核,最终在`/dev`目录生成设备文件[^2][^4]。 4. **关联操作函数** 绑定设备节点的文件操作函数(如`open`、`read`、`write`),实现用户空间与硬件的交互。 #### 二、probe函数的实现流程 1. **参数解析** 通过`struct platform_device *pdev`获取设备信息(如设备树中的配置或ACPI参数),例如使用`platform_get_resource`获取资源。 2. **硬件检查与初始化** 验证设备是否响应,并配置寄存器。例如,通过I2C总线发送探测指令确认设备存在。 3. **资源分配** ```c // 示例:申请中断 int irq = platform_get_irq(pdev, 0); request_irq(irq, interrupt_handler, IRQF_SHARED, "my_device", dev); ``` 4. **设备节点注册** ```c // 初始化字符设备 cdev_init(&my_cdev, &my_fops); my_cdev.owner = THIS_MODULE; cdev_add(&my_cdev, dev_num, 1); ``` 5. **文件操作函数绑定** ```c static struct file_operations my_fops = { .owner = THIS_MODULE, .open = my_device_open, .read = my_device_read, .release = my_device_release, }; ``` #### 三、probe函数调用时机 当内核检测到**设备与驱动匹配**时,总线(如platform总线、I2C总线)会调用probe函数。匹配条件包括: - 设备树中的`compatible`属性与驱动定义的`of_match_table`匹配。 - ACPI ID或设备名称匹配[^1][^3]。 #### 四、示例代码 ```c static int my_driver_probe(struct platform_device *pdev) { struct my_device *dev; // 1. 分配设备结构体 dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); // 2. 获取资源(如寄存器地址) dev->regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 3. 初始化硬件 if (my_device_hw_init(dev) < 0) { dev_err(&pdev->dev, "硬件初始化失败"); return -ENODEV; } // 4. 注册字符设备 cdev_init(&dev->cdev, &my_fops); cdev_add(&dev->cdev, dev_num, 1); // 5. 保存设备上下文 platform_set_drvdata(pdev, dev); return 0; } ``` #### 五、常见问题 1. **参数来源** probe函数的参数(如`struct platform_device *pdev`)由内核自动传递,包含设备在总线上的信息[^5]。 2. **错误处理** 若probe函数返回非零值,内核会标记驱动加载失败,并可能卸载模块。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值