分类: LINUX
2012-05-25 11:30:32
Realtek8168网卡时pci接口的网卡,其驱动程序就是一个PCI设备的驱动程序实例,我们一起看看其流程。
1. 首先,初始化模块调用static inline int pci_register_driver(struct pci_driver *driver)函数来注册设备驱动,这个函数的参数是struct pci_driver *driver,对应于r8168,就是
static struct pci_driver rtl8168_pci_driver = {
.name = MODULENAME,
.id_table = rtl8168_pci_tbl,
.probe = rtl8168_init_one,
.remove = __devexit_p(rtl8168_remove_one),
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,11)
.shutdown = rtl8168_shutdown,
#endif
#ifdef CONFIG_PM
.suspend = rtl8168_suspend,
.resume = rtl8168_resume,
#endif
};
这个结构体把这个设备驱动所支持的设备 (rtl8168_pci_tbl),探测函数(rtl8168_init_one)等都定义好,后面我们将需要用到 rtl8168_pci_tbl,rtl8168_init_one两部分内容来匹配,是否系统中的设备,看是否有设备可以跟这个驱动匹配
2. pci_register_driver 函数调用__pci_register_driver来完成任务,而__pci_register_driver则重新封装了要注册的驱动为PCI总线的,即
int __pci_register_driver(struct pci_driver *drv, struct module *owner)
{
……
drv->driver.bus = &pci_bus_type;
……
drv->driver.kobj.ktype = &pci_driver_kobj_type;
……
}
接下来就是调用设备驱动模型的函数,把我们要注册的驱动挂载到PCI总线的设备队列上,并扫描PCI总线的设备队列,查看是否有设备可以匹配这个驱动,这跟usb设备驱动的挂载是一致的,只是这里挂载的是PCI总线,usb挂载的是USB总线,大致的流程是
driver_register()---àbus_add_driver()----àdriver_attach()--à__driver_attach()--àdriver_probe_device()---àdev->bus->probe(), 即最后还是调用了2.中的pci_bus_type结构体中的probe成员函数,即static int pci_device_probe(struct device * dev)
3. static int pci_device_probe(struct device * dev)函数的参数dev就是遍历了PCI总线上的设备链表,一一进行匹配来完成的,因为我们调用__driver_attach()的方式是 bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
4. static int pci_device_probe(struct device * dev)通过两个宏转换to_pci_driver,to_pci_dev,获得需要匹配的设备和驱动,调用static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)函数进行匹配
5. __pci_device_probe函数首先通过设备驱动中的rtl8168_pci_tbl表,跟从设备获得vendorID,productID进 行比较,看是否一致,如果一致,就返回这个表的地址;如果没有一致的,就表明,这个设备跟这个驱动不匹配,就不需要继续进行下面的操作了,直接退出
6. 如果第5步发现了一致的设备表,就表明有设备ID一致,需要进一步探测,接下来就要调用我们设备驱动程序中的探测函数,进行更具体的探测了,即 pci_call_probe(drv, pci_dev, id)---à drv->probe(dev, id),到这里,就开始调用我们的设备驱动中的探测函数了。
7. static int __devinit rtl8168_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)函数是r8168的探测函数,其调用rtl8168_init_board(pdev, &dev, &ioaddr)来完成跟PCI设备驱动相关的探测。
8. static int __devinit rtl8168_init_board(struct pci_dev *pdev, struct net_device **dev_out, void __iomem **ioaddr_out)函数调用pci_enable_device函数来使能PCI设备,只有使能成功的PCI设备,才能正常使用。
9. 调用pci_set_mwi函数判断设备是否支持memory-write-invalidate 功能
10. 调用pci_find_capability函数来判断设备是否有电源管理功能.
11. 调用pci_resource_flags函数来判断PCI是内存映射模式,还是IO模式
12. 调用pci_resource_len函数来判断内存空间是否小于设备所需要的内存空间,如果小于,明显出错
13. 调用pci_request_regions函数通知内核,当前PCI将使用这些内存地址,其他设备不能再使用了
14. 调用pci_set_master(pdev)函数,设置设备具有获得总线的能力,即调用这个函数,使设备具备申请使用PCI总线的能力。
15. 调用ioremap函数把刚刚申请的物理内存,映射成虚拟内存,因为进程使用的都是虚拟内存地址,而不是物理内存地址。
16. 把ioremap映射的虚拟内存返回给调用函数。
17. 到此,跟PCI相关的初始化都完成了,设备即可正常工作了
zj199210042015-03-12 10:55:08
您好,我最近刚学PCIE设备驱动的编写,有一些问题还不是很懂,请教一下:
.是不是在程序中只用写好driver里面对应的函数,调用pci_register_driver 就好了 后面的2-16都有内核帮我们做好?
如果您有PCIE驱动的源代码,愿意提供一下的话,非常感谢!我的邮箱:1241225106@qq.com