版权声明:本文为博主原创文章,未经博主允许不得转载。
https://blog.csdn.net/huangweiqing80/article/details/83347495
一、PCI设备驱动编写
PCI总线是现在非常流行的计算机总线,学会它的驱动设计方法很重要。相信曾经想学习PCI总线驱动的人有这么一个经历,就是去看那些讲解PCI总线驱动的书籍和资料的时候,会被里面繁杂的内容所击败,又是什么配置空间又是什么枚举的,还没开始真正的去写PCI的驱动,到这里就已经开始打退堂鼓了。其实,只要你认真下去,虽然有些东西看不明白,但是对于你写PCI的驱动来说,似乎“不那么重要”。因为,Linux内核对PCI总线已经有了完美的支持,你所需要做的内容是非常小的一部份。
Linux下的PCI总线,在系统上电的时候会逐一的扫描系统中存在的设备(包括设备和桥),总线号中断号都是这个时候分配给设备的,如果你是初学者,这个过程如果不是很明白,你大可以先略过,去找一个带有PCI总线的开发板,接上PCI的设备,让系统重启扫描一遍,再配合下面会给出的PCI总线驱动框架,你就会明白很多。
众所周知,Linux 2.6内核引入了总线驱动模型这一概念,如此,很多基于总线的设备驱动就分成了总线驱动和设备驱动两部分。其实PCI总线驱动跟2.6内核里面的platform总线有类似之处,只不过platform总线的匹配方式是名字匹配,也就是设备名和驱动名一致。PCI总线匹配的是id_table;但匹配方式不只一种,最常见的就是厂商号和设备号。当你加载PCI驱动的时候,驱动程序会把系统中已经存在的设备的厂商号和设备号与驱动程序中的对比,如果一致,则会注册PCI总线驱动并进行下一步操作。
对于PCI总线上电扫描过程,推荐去看一篇博客,http://blog.csdn.net/linuxdrivers/article/details/5849698,他讲的详细一点。
下面是我写的一个PCI总线的驱动程序,注意是PCI设备识别时的驱动程序,这里并没有实现具体的功能驱动。PCI设备的驱动分成两个部分,一部分是总线的,就是PCI设备识别、调用驱动程序probe函数的部分,另一部分就是具体的功能驱动,比如网卡。基于PCI总线的设备有很多种,但就PCI总线驱动这一块来说,都大同小异,实现了PCI总线驱动之后,再去继续做具体的设备驱动。
程序如下(在2.6.31至3.1.4内核都可以运行成功):
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
//设备相关
#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 = %d\n",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 /] insmod ar9280.ko
Probe succeeds.PCIE ioport addr start at 98000000, mypci->ioaddr is 0xd4fa0000,interrupt No.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 设备的pin list
扯点题外话,里面大部分信号是低电平有效。据说是因为低电平阻抗低,抗干扰能力强。
可以看到,它有四个中断pin,但是它是放在右边作为optional 的。
在PCI 里面,中断是电平触发的,低电平有效,如果不是走MSI方式,当Device 有需要的时候,Device driver 会去拉低INTx line. 一旦这个信号被拉低,它会持续为低,直到Driver 没有了pending 请求。如果是单功能设备,那么只需要用到INT A,多功能设备可以把INT A, B, C ,D 都用完。
对于多功能设备而言,上的的逻辑设备可以使用A, B, C ,D 中的任何一根。
从上面我们可以看出,每个PCI设备都含有四个IO口INTA# - INTD#,设备的中断引脚( INTA# - INTD#)会连接到系统中断控制器的引脚(1RQO - IRQ15中)上去,这样当INTA# - INTD#引脚拉低时,就相当于把连接到中断控制器中的中断引脚拉低了,从而产生中断。
关于PCI中断还可以参考:
https://blog.csdn.net/shanghaiqianlun/article/details/7100825?utm_source=blogxgwz9