PCI总线在计算机界颇受欢迎,掌握其驱动程序设计的重要性不言而喻。Linux系统对PCI总线的支持相当到位,这表面上似乎减少了开发者的负担,然而对于新手而言,仍有许多疑惑有待解开。
PCI总线在Linux下的上电扫描
在Linux系统中,系统启动时会对PCI总线进行设备扫描,这一过程涵盖了设备和桥接器等。比如在某个特定的开发板上,系统启动后会为设备分配总线号和中断号。这个过程对于初学者来说可能有些难以理解。不过,初学者可以先暂时跳过这一步骤。可以选择一块带有PCI总线的开发板,连接PCI设备后重启系统进行扫描,然后再结合驱动程序框架来学习。比如我在实验室使用的开发板,在连接网卡并重启后,可以看到设备被扫描并识别出来。这实际上就是电脑启动时内部的工作流程之一。
实际操作中,若对扫描过程的细节有所好奇,不妨查阅相关资料进行深入研究。理论结合实际,这能让我们更深入地理解和掌握这一关键扫描过程。
Linux2.6内核下的总线驱动模型
Linux2.6内核推出的总线驱动模型颇具特色。此后,总线设备驱动被细分为总线驱动和设备驱动两个独立部分。以PCI总线驱动为例,它与2.6内核中的总线驱动存在相似之处。其匹配原则是名称对应,即设备名称需与驱动名称相同。若要使某PCI设备正常运行,必须确保设备名与驱动名相符。有开发者在测试过程中,因名称不匹配,导致驱动无法正确加载。这种匹配机制有效保障了系统的稳定性和准确性。
正确理解这种模式对于开发PCI总线驱动来说至关重要。若不依照这种匹配方式,实际使用时驱动程序将难以找到设备,或者设备无法识别驱动程序,这些问题将严重妨碍设备的正常运行。
PCI驱动加载过程
在安装PCI驱动过程中,需要进行一系列的判断。首先,它会核对系统内现有设备的厂商号和设备号,与驱动程序中的数据相对比。如果匹配成功,便会注册PCI总线驱动,并继续执行后续步骤。这个过程颇似一个严谨的身份验证过程。在我的工作中,曾经遇到过因厂商号出错,导致驱动程序无法成功注册的情况。经过仔细检查,我发现错误源于设备厂商号在生产过程中被错误录入。
这一过程极为严格,稍有不慎,小错误也可能引发驱动加载失败。因此,开发人员必须细心操作。比如,他们需要多次检查设备信息与驱动数据是否一致,这样才能有效降低出错率。
PCI总线驱动的组成部分
PCI设备驱动主要由两部分构成。首先是总线相关部分,涉及PCI设备识别、调用驱动程序的probe函数等操作。其次是具体的功能驱动,比如网卡驱动。在众多采用PCI总线的设备中,如声卡、显卡等,总线驱动模块的设计是相似的。我们首先完成了PCI总线驱动的开发,随后具体设备的驱动开发工作也就变得较为顺利。在测试PCI声卡设备时,我已成功处理了PCI总线驱动,这使得后续的功能驱动开发过程变得更加顺畅。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//设备相关
#define MY_VENDOR_ID 0x168c //厂商号
#define MY_DEVICE_ID 0x002a //设备号
#define MY_PCI_NAME "MYPCIE" //自己起的设备名
static int debug = 1;
module_param(debug,int,S_IRUGO);
#define DBG(msg...) do{
if(debug)
printk(msg);
}while(0)
struct pcie_card
{
//端口读写变量
int io;
long range,flags;
void __iomem *ioaddr;
int irq;
};
/* 设备中断服务*/
static irqreturn_t mypci_interrupt(int irq, void *dev_id)
{
struct pcie_card *mypci = (struct pcie_card *)dev_id;
printk("irq = %d,mypci_irq = %dn",irq,mypci->irq);
return IRQ_HANDLED;
}
/* 探测PCI设备*/
static int __init mypci_probe(struct pci_dev *dev, const struct pci_device_id *ent)
{
int retval=0;//, intport, intmask;
struct pcie_card *mypci;
if ( pci_enable_device (dev) )
{
printk (KERN_ERR "IO Error.n");
return -EIO;
}
/*分配设备结构*/
mypci = kmalloc(sizeof(struct pcie_card),GFP_KERNEL);
if(!mypci)
{
printk("In %s,kmalloc err!",__func__);
return -ENOMEM;
}
/*设定端口地址及其范围,指定中断IRQ*/
mypci->irq = dev->irq;
if(mypci->irq < 0)
{
printk("IRQ is %d, it's invalid!n",mypci->irq);
goto out_mypci;
}
mypci->io = pci_resource_start(dev, 0);
mypci->range = pci_resource_end(dev, 0) - mypci->io;
mypci->flags = pci_resource_flags(dev,0);
DBG("PCI base addr 0 is io%s.n",(mypci->flags & IORESOURCE_MEM)? "mem":"port");
/*检查申请IO端口*/
retval = check_region(mypci->io,mypci->range);
if(retval)
{
printk(KERN_ERR "I/O %d is not free.n",mypci->io);
goto out_mypci;
}
//request_region(mypci->io,mypci->range, MY_PCI_NAME);
retval = pci_request_regions(dev,MY_PCI_NAME);
if(retval)
{
printk("PCI request regions err!n");
goto out_mypci;
}
mypci->ioaddr = ioremap(mypci->io,mypci->range);
if(!mypci->ioaddr)
{
printk("ioremap err!n");
retval = -ENOMEM;
goto out_regions;
}
//申请中断IRQ并设定中断服务子函数
retval = request_irq(mypci->irq, mypci_interrupt, IRQF_SHARED, MY_PCI_NAME, mypci);
if(retval)
{
printk (KERN_ERR "Can't get assigned IRQ %d.n",mypci->irq);
goto out_iounmap;
}
pci_set_drvdata(dev,mypci);
DBG("Probe succeeds.PCIE ioport addr start at %X, mypci->ioaddr is 0x%p,interrupt No. %d.n",mypci->io,mypci->ioaddr,mypci->irq);
return 0;
out_iounmap:
iounmap(mypci->ioaddr);
out_regions:
pci_release_regions(dev);
out_mypci:
kfree(mypci);
return retval;
}
/* 移除PCI设备 */
static void __devexit mypci_remove(struct pci_dev *dev)
{
struct pcie_card *mypci = pci_get_drvdata(dev);
free_irq (mypci->irq, mypci);
iounmap(mypci->ioaddr);
//release_region(mypci->io,mypci->range);
pci_release_regions(dev);
kfree(mypci);
DBG("Device is removed successfully.n");
}
/* 指明驱动程序适用的PCI设备ID */
static struct pci_device_id mypci_table[] __initdata =
{
{
MY_VENDOR_ID, //厂商ID
MY_DEVICE_ID, //设备ID
PCI_ANY_ID, //子厂商ID
PCI_ANY_ID, //子设备ID
},
{0, },
};
MODULE_DEVICE_TABLE(pci, mypci_table);
/* 设备模块信息 */
static struct pci_driver mypci_driver_ops =
{
name: MY_PCI_NAME, //设备模块名称
id_table: mypci_table, //驱动设备表
probe: mypci_probe, //查找并初始化设备
remove: mypci_remove //卸载设备模块
};
static int __init mypci_init(void)
{
//注册硬件驱动程序
if ( pci_register_driver(&mypci_driver_ops) )
{
printk (KERN_ERR "Can't register driver!n");
return -ENODEV;
}
return 0;
}
static void __exit mypci_exit(void)
{
pci_unregister_driver(&mypci_driver_ops);
}
module_init(mypci_init);
module_exit(mypci_exit);
MODULE_LICENSE("GPL");
这两个部分的开发顺序是有一定逻辑的。若顺序颠倒,开发过程中很容易出现逻辑上的混乱,进而影响开发效率,甚至可能导致设备驱动失败。
PCI设备识别实例
我进行了一项实验,在开发板上接入了一块PCIE网卡。系统重启后,加载相应的驱动模块,便启动了驱动注册等步骤。当系统显示“探测成功”时,说明系统已识别出我的网卡。这正显示了成功识别PCI设备的过程。这一成果表明,系统的PCI总线物理起始地址已被正确识别。这标志着开发过程中的一个关键节点,若识别失败,就必须重新检查设备、驱动、硬件连接等问题。
PCI设备在识别上或许各有不同,但它们的识别逻辑大体相近。通过这个例子,我们可以更深入地了解PCI设备识别的运作原理。
[root@board /] cat /proc/interrupts
CPU0
17: 0 UIC Level MYPCIE
18: 24 UIC Level MAL TX EOB
19: 225 UIC Level MAL RX EOB
20: 0 UIC Level MAL SERR
21: 0 UIC Level MAL TX DE
22: 0 UIC Level MAL RX DE
24: 0 UIC Level EMAC
26: 1194 UIC Level serial
BAD: 0
[root@board /] cat /proc/iomem //注意:查看iomem时出现了自己的设备占用的iomem,说明是IO内存
90000000-97ffffff : /plb/pciex@0a0000000
98000000-9fffffff : /plb/pciex@0c0000000
98000000-980fffff : PCI Bus 0001:41
98000000-9800ffff : 0001:41:00.0
98000000-9800ffff : MYPCIE
ef600200-ef600207 : serial
ef600300-ef600307 : serial
fc000000-ffffffff : fc000000.nor_flash
PCI中的中断机制
在PCI中,中断是由电平触发,且低电平为有效信号。如果不采用MSI方式,一旦有需求,就会将INTx线路拉低,并保持低电平状态,直到请求结束。每个PCI设备都配备了四个IO口,即INTA#至INTD#,这些中断引脚连接到系统中的中断控制器,以产生中断。这种机制对于设备运行至关重要。以我的网卡设备为例,当它需要处理数据请求时,就会通过触发中断机制与系统进行交互。
合理运用中断机制能提升PCI设备的表现,若对这一机制不甚了解,在设备发生中断故障时,可能难以准确找到问题所在。
PCI总线的驱动设计在Linux系统下得到了较好的支持,简化了不少工作,但其中仍有许多细节需要我们特别留意。在开发PCI总线驱动时,你是否遇到过一些令人印象深刻的问题?欢迎点赞、分享这篇文章,并踊跃留言评论。