对于PCI的学习,在文章《初识PCI》和《再识PCI:一个PCI驱动实例》中有介绍,文中使用大量代码进行演示。但总觉得有些认知不到位。于是就再写一文。
一、PCI驱动一般框架
先看一下PCI驱动一般框架的示例代码:
#include <linux/pci.h>
#include <linux/module.h>
static int misc_pci_probe(struct pci_dev *dev,
const struct pci_device_id *ent)
{
switch (dev->device)
{
case 0x0f1c:
printk("found LPC pci device...\n");
break;
case 0x0f12:
printk("found SMBus pci device...\n");
// 此处可注册设备,如platform设备
// platform_device_register
// 设置PCI驱动数据,在本驱动退出时,可获取到paltform设备从而卸载之
pci_set_drvdata(dev, pdev);
break;
default:
printk("unknown pci device 0x%x...\n", dev->device);
break;
}
return ret;
}
static void misc_pci_remove(struct pci_dev *dev)
{
// 得到platform设备,卸载
struct platform_device *pdev = pci_get_drvdata(dev);
platform_device_unregister(pdev);
}
// 在这里指定PCI设备ID,PCI_VDEVICE会组装厂家ID和PCI设备ID,后面的是驱动私有数据,可以传递必要的信息。当然也可不写
static const struct pci_device_id misc_pci_tbl[] = {
{ PCI_VDEVICE(INTEL, 0x0f1c), 0 },
{ PCI_VDEVICE(INTEL, 0x0f12), 1 }, // SMBus
{ },
};
MODULE_DEVICE_TABLE(pci, misc_pci_tbl);
static struct pci_driver misc_pci_driver = {
.name = "misc_pci",
.id_table = misc_pci_tbl,
.probe = misc_pci_probe,
.remove = misc_pci_remove,
};
// 注册PCI驱动
module_pci_driver(misc_pci_driver);
MODULE_DESCRIPTION("PCI driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Late Lee<latelee@163.org>");
二、几点说明
1、内核启动时根据深度优先方式扫描PCI总线上的设备。比如,当前的PCI设备是PCI桥的话,就会继续扫描其下的设备,直到没有为止。
2、在系统启动时,扫描PCI,找到PCI设备,如果该设备已经注册驱动,则调用驱动的probe函数。如果PCI设备已经有了对应的驱动,则不会再被调用。以上述代码为例,假如系统启动时已经有了ID为0x0f1和0x0f12的驱动,则上述的probe函数不会被调用到。可以认为当前已经有驱动占用着这2个设备。在《再识PCI:一个PCI驱动实例》一文中,还指定了E1000_DEV_ID_I211_COPPER这个ID,但没有识别出来。是因为其对应的IGB驱动被加载了。当然,如果当前不存在对应的驱动,就会调用到probe函数。
3、有多个相同设备的也会继续调用probe。直到扫描结束。比如,有2个相同的网卡设备(即网卡芯片ID相同)处于不同的PCI总线情况时,它们对应pci_device_id只有一个ID。但它们在不同PCI总线上,当扫描到设备,会调用2次对应驱动的probe函数。
三、bus(总线)、driver(驱动)、device(设备)
顺着PCI驱动,再概述一下bus、driver、device这几个概念。
一条bus上挂着许多device,而device需要driver才能工作。内核有注册device和注册driver的概念。注册device只是将deivce添加到内核中,该设备还不能工作。而注册driver,就是在对应的bus上找到device,从而调用driver的probe函数进行初始化,而后继续其它的事情。
device和driver通过某些方式匹配,就能正常工作。比如platform设备根据name来匹配的,PCI设备根据ID匹配。所以看到很多内核代码在定义platform_device时指定name,而在定义platform_driver时指定driver成员的name,因为只有这样两者才能匹配。一般地,发行版本的Linux系统都带有很多驱动。如前段时间研究WIFI,将Intel无线网卡和atheros无线网卡插到安装有发行版本的系统的设备上,就可以正常工作。
在/sys/bus/目录下有当前系统各种bus。比如i2c总线、PCI总线、SPI总线、platform总线。每种bus目录均有device和driver,在其中列出该总线上所有的设备和对应的驱动。比如platform设备、驱动分别在/sys/bus/platform/devices和/sys/bus/platform/drivers这两个目录。