PCI总线驱动程序

来源:互联网 发布:outlook是什么软件 编辑:程序博客网 时间:2024/05/22 16:52

        PCI总线是现在非常流行的计算机总线,学会它的驱动设计方法很重要。相信曾经想学习PCI总线驱动的人有这么一个经历,就是去看那些讲解PCI总线驱动的书籍和资料的时候,会被里面繁杂的内容所击败,又是什么配置空间又是什么枚举的,还没开始真正的去写PCI的驱动,到这里就已经开始打退堂鼓了。其实,只要你认真下去,虽然有些东西看不明白,但是对于你写PCI的驱动来说,似乎“不那么重要”。因为,Linux内核对PCI总线已经有了完美的支持,你所需要做的内容是非常小的一部份。

       Linux下的PCI总线,在系统上电的时候会逐一的扫描系统中存在的设备(包括设备和桥),总线号中断号都是这个时候分配给设备的,如果你是初学者,这个过程如果不是很明白,你大可以先略过,去找一个带有PCI总线的开发板,接上PCI的设备,让系统重启扫描一遍,再配合下面会给出的PCI总线驱动框架,你就会明白很多。

       众所周知,Linux 2.6内核引入了总线驱动模型这一概念,如此,很多基于总线的设备驱动就分成了总线驱动和设备驱动两部分。其实PCI总线驱动跟2.6内核里面的platform总线有类似之处,只不过platform总线的匹配方式是名字匹配,也就是设备名和驱动名一致。PCI总线的匹配方式不只一种,最常见的就是厂商号和设备号。当你加载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总线驱动已经加载成功。后续可以继续做设备驱动的内容了。