为了技术,我不会停下学习的脚步,我相信我还能走二十年。
分类: LINUX
2013-02-28 11:05:56
原文地址:Linux网络驱动源码分析(一) 作者:liujunwei1234
网络驱动是一种典型的PCI设备驱动,无论在嵌入式平台还是在PC领域,网络相关的项目开发有着比较广阔的前景,因此,分析当前Linux内核中网络设备的驱动,不但能了解网络相关的基本原理,而且可以借鉴Linux内核的先进的技术,将其应用到嵌入式或其他领域。本文以Linux内核中的rtl8139网络驱动为例,对网络驱动的源码进行了简单分析,并对其中涉及的相关概念和技术进行了简单的介绍。
一、PCI设备驱动模型
rtl8139是典型的PCI设备,Linux内核的PCI核心驱动为PCI驱动开发者提供了方便的系统接口,极大地方便了PCI设备驱动的开发。
1 pci设备驱动相关的数据结构
pci设备描述结构体
驱动开发者要想为某个PCI设备开发驱动就必须定义一个与当前PCI设备相对应的pci_driver数据结构,用来描述将要开发的pci驱动的相关信息,比如驱动的名称,当前驱动可以支持哪些设备,以及当前驱动支持的一些操作等,类似地,还需要有个结构体来表示PCI设备,描述PCI设备的硬件信息,如厂商ID,设备ID,以及各种资源等,详见注释。
二、PCI核心驱动API
Linux内核的PCI驱动为PCI设备驱动的开发提供了方便的结构,下面列举几个常用的接口:
pci_register_driver(struct pci_driver *drv)
功能:注册PCI驱动,参数为要注册的pci驱动的结构体。
下面来详细的分析以下这个函数,如此,才能更清楚的了解驱动和设备的匹配过程。
- pci_register_driver->driver_register(&drv->driver);->bus_add_driver->driver_attach->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
在这个过程中有涉及到一个更为抽象的结构体struct device_driver,它是pci_driver的更高级的抽象,即下层是pci_driver,其上是device_driver,这符合通常的程序设计逻辑,越往上层抽象级别越高,因为在操作系统看来,它并不需要知道具体是什么设备,所有的设备对操作系统来说都是相同的,即都用struct device_driver来表示。
在driver_register中先调用driver_find(drv->name, drv->bus),首先在相应的总线上查找drv->name的驱动是否已经被注册过,如果被注册过则返回,否则进行注册过程,即调用bus_add_driver(drv)。
int bus_add_driver(struct device_driver *drv)函数首先判断当前总线是否支持自动探测,如果执行则执行探测函数driver_attach(drv)。
- if (drv->bus->p->drivers_autoprobe) {
- error = driver_attach(drv);
- if (error)
- goto out_unregister;
- }int driver_attach(struct device_driver *drv)
- {
- return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
- }
这个函数对PCI总线的上所有已经连接的PCI设备与当前的PCI驱动进程一次匹配的过程,即对每一个PCI设备都调用匹配函数__driver_attach。
- static int __driver_attach(struct device *dev, void *data)
- {
- struct device_driver *drv = data;
- /*
- * Lock device and try to bind to it. We drop the error
- * here and always return 0, because we need to keep trying
- * to bind to devices and some drivers will return an error
- * simply if it didn't support the device.
- *
- * driver_probe_device() will spit a warning if there
- * is an error.
- */
- if (!driver_match_device(drv, dev))
- return 0;
- if (dev->parent) /* Needed for USB */
- device_lock(dev->parent);
- device_lock(dev);
- if (!dev->driver)
- driver_probe_device(drv, dev);
- device_unlock(dev);
- if (dev->parent)
- device_unlock(dev->parent);
- return 0;
- }
该函数首先判断总线提供的match函数是否为空,如果非空则执行总线提供的match函数,在rtl8139网络驱动中,match非空,参见代码:
- drv->driver.bus = &pci_bus_type;struct bus_type pci_bus_type = {
- .name = "pci",
- .match = pci_bus_match,
- .uevent = pci_uevent,
- .probe = pci_device_probe,
- .remove = pci_device_remove,
- .shutdown = pci_device_shutdown,
- .dev_attrs = pci_dev_attrs,
- .bus_attrs = pci_bus_attrs,
- .pm = PCI_PM_OPS_PTR,
- };
这里将match函数即pci_bus_match,最后该函数调用到
- static inline const struct pci_device_id *
- pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
- {
- if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
- (id->device == PCI_ANY_ID || id->device == dev->device) &&
- (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
- (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
- !((id->class ^ dev->class) & id->class_mask))
- return id;
- return NULL;
- }
在这里进行了pci_driver和pci_dev的匹配,如果匹配成功,则返回pci_device_id。如果匹配不成功,而且当前设备还没有驱动,则调用driver_probe_device(drv,dev)。
- 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);
- pm_runtime_get_noresume(dev);
- pm_runtime_barrier(dev);
- ret = really_probe(dev, drv);
- pm_runtime_put_sync(dev);
- return ret;
- }
执行到这里说明,说明PCI总线没有提供match函数或者总线提供的match函数返回非空。还需要进行更深层次的探测,至少在总线提供的match函数中仅仅是进行了匹配,并没有将驱动和设备关联起来,这些操作就是在下面的函数中实现的。
- 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);
- pm_runtime_get_noresume(dev);
- pm_runtime_barrier(dev);
- ret = really_probe(dev, drv);
- pm_runtime_put_sync(dev);
- return ret;
- }
重点看really_probe函数:
- static int really_probe(struct device *dev, struct device_driver *drv)
- {
- int ret = 0;
- 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 (driver_sysfs_add(dev)) {
- printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
- __func__, dev_name(dev));
- goto probe_failed;
- }
- if (dev->bus->probe) {
- ret = dev->bus->probe(dev);
- if (ret)
- goto probe_failed;
- } else if (drv->probe) {
- ret = drv->probe(dev);
- if (ret)
- goto probe_failed;
- }
- driver_bound(dev);
- 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;
- probe_failed:
- devres_release_all(dev);
- driver_sysfs_remove(dev);
- dev->driver = NULL;
- if (ret != -ENODEV && ret != -ENXIO) {
- /* driver matched but the probe failed */
- printk(KERN_WARNING
- "%s: probe of %s failed with error %d\n",
- drv->name, dev_name(dev), ret);
- }
- /*
- * Ignore errors returned by ->probe so that the next driver can try
- * its luck.
- */
- ret = 0;
- done:
- atomic_dec(&probe_count);
- wake_up(&probe_waitqueue);
- return ret;
- }
在此函数中,首先将驱动和设备关联起来,即红色代码dev->driver = drv; 指明了当前设备的驱动。按照常规的程序设计思想,驱动和设备关联后是否还需要做一些其他工作才能是设备在相应的驱动下正常工作呢,这就是probe函数实现的功能了,很明显设备和驱动获取都需要做一些工作,因此这里分别留出设备和驱动的probe函数。其中设备的probe即设备所在总线的probe,这里暂且不去分析,因为与网络驱动关系不大,都是PCI总线相关的东西,重点来看驱动的probe,在前面提到的pci_driver结构体中,对于rtl8139驱动来说,其pci_driver结构体被初始化为:
- static struct pci_driver rtl8139_pci_driver = {
- .name = DRV_NAME,
- .id_table = rtl8139_pci_tbl,
- .probe = rtl8139_init_one,
- .remove = __devexit_p(rtl8139_remove_one),
- #ifdef CONFIG_PM
- .suspend = rtl8139_suspend,
- .resume = rtl8139_resume,
- #endif /* CONFIG_PM */
- };
这里即调用rtl8139_init_one,经过上面的逐层分析,我们从pci核心驱动一步一步的走到了rtl8139网络设备的驱动,豁然开朗了,以后看网络驱动的时候就不会感到开始的地方有点迷糊了。代码分析重在代码之间的过渡,如果衔接不好,很多地方都会产生疑问。在下一篇文章中,将会详细分析rtl8139网络驱动的代码。