一、PCI设备驱动编撰
PCI总线是现今十分流行的计算机总线,学会它的驱动设计方式很重要。相信当初想学习PCI总线驱动的人有那么一个经历,就是去看这些讲解PCI总线驱动的书籍和资料的时侯,会被上面冗长的内容所打败,又是哪些配置空间又是哪些枚举的,还没开始真正的去写PCI的驱动多个pci设备 linux驱动,到这儿就早已开始打退堂鼓了。虽然,只要你认真下去,即使有些东西看不明白,并且对于你写PCI的驱动来说,虽然“不这么重要”。由于,linux内核对PCI总线早已有了完美的支持,你所须要做的内容是十分小的一部分。
Linux下的PCI总线,在系统上电的时侯会逐一的扫描系统中存在的设备(包括设备和桥),总线号中断号都是这个时侯分配给设备的,假如你是初学者,这个过程倘若不是很明白,你大可以先略过,去找一个带有PCI总线的开发板,接上PCI的设备,让系统重启扫描一遍,再配合下边会给出的PCI总线驱动框架,你都会明白好多。
众所周知,Linux2.6内核引入了总线驱动模型这一概念,这般,好多基于总线的设备驱动就分成了总线驱动和设备驱动两部份。虽然PCI总线驱动跟2.6内核上面的platform总线有类似之处,只不过platform总线的匹配方法是名子匹配,也就是设备名和驱动名一致。PCI总线匹配的是id_table;但匹配方法不只一种,最常见的就是厂商号和设备号。当你加载PCI驱动的时侯,驱动程序会把系统中早已存在的设备的厂商号和设备号与驱动程序中的对比,假如一致,则会注册PCI总线驱动并进行下一步操作。
对于PCI总线上电扫描过程,推荐去看一篇博客,,他讲的详尽一点。
下边是我写的一个PCI总线的驱动程序,注意是PCI设备辨识时的驱动程序,这儿并没有实现具体的功能驱动。PCI设备的驱动分成两个部份,一部份是总线的,就是PCI设备辨识、调用驱动程序probe函数的部份,另一部份就是具体的功能驱动,例如网卡。基于PCI总线的设备有好多种,但就PCI总线驱动这一块来说,都邯郸小异,实现了PCI总线驱动以后,再去继续做具体的设备驱动。
程序如下(在2.6.31至3.1.4内核都可以运行成功):
#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");
以上这个程序是我在开发板中插入了一个PCIE的网卡设备,系统重启以后,加载这个驱动模块,才会进行注册驱动等一系列的操作。
加载模块后的结果:
[root@board/]insmodar9280.ko
Probesucceeds.PCIEioportaddrstartat98000000,mypci->ioaddris0xd4fa0000,interruptNo.17.
看见里面Probe成功,说明系统找到了我的网卡,98000000正是系统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中的中断
下边来讲一下PCI中断:
首先看一下pci设备的pinlist
扯点正题,上面大部份讯号是低电平有效。听说是由于低电平阻抗低,抗干扰能力强。
可以看见,它有四个中断pin,并且它是置于左侧作为optional的。
在PCI上面,中断是电平触发的,低电平有效,倘若不是走MSI形式多个pci设备 linux驱动,当Device有须要的时侯,Devicedriver会去拉低INTxline.一旦这个讯号被拉低,它会持续为低,直至Driver没有了pending恳求。假如是单功能设备linux定时器,这么只须要用到INTAlinux模拟,多功能设备可以把INTA,B,C,D都用完。
对于多功能设备而言,上的的逻辑设备可以使用A,B,C,D中的任何一根。
从前面我们可以看出,每位PCI设备都富含四个IO口INTA#-INTD#,设备的中断引脚(INTA#-INTD#)会联接到系统中断控制器的引脚(1RQO-IRQ15中)起来,这样当INTA#-INTD#引脚拉低时,就相当于把联接到中断控制器中的中断引脚拉低了,因而形成中断。
关于PCI中断还可以参考: