Linux PCI 設備驅動基本框架(二)


針對相應設備定義描述該PCI設備的數據結構:

struct device_private
{

     /*注冊字符驅動和發現PCI設備的時候使用*/
     struct pci_dev  *my_pdev;//
     struct cdev my_cdev;//

     dev_t my_dev;
     atomic_t created;


      /* 用於獲取PCI設備配置空間的基本信息 */
     unsigned long mmio_addr;
     unsigned long regs_len;
     int     irq;//中斷號
    
     /*用於保存分配給PCI設備的內存空間的信息*/
     dma_addr_t rx_dma_addrp;
     dma_addr_t tx_dma_addrp;


     /*基本的同步手段*/

     spinlock_t lock_send;
     spinlock_t lock_rev;


     /*保存內存空間轉換后的地址信息*/
     void __iomem *ioaddr;
     unsigned long virts_addr;


      int open_flag // 設備打開標記

     .....
    
};

 

初始化設備模塊:  

static struct pci_driver my_pci_driver = {
     name:     DRV_NAME,  // 驅動的名字,一般是一個宏定義
     id_table:     my_pci_tbl, //包含了相關物理PCI設備的基本信息,vendorID,deviceID等
     probe:     pci_probe, //用於發現PCI設備
     remove:     __devexit_p(pci_remove), //PCI設備的移除
};

 

// my_pci_tbl 其實是一個 struct pci_device 結構,該結構可以有很多項,每一項代表一個設備

// 該結構可以包含很多項,每一項表明使用該結構的驅動支持的設備

// 注意:需要以一個空的項結尾,也就是:{0,}

static struct pci_device_id my_pci_tbl[] __initdata = {
     { vendor_id, device_id, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
     { 0,}
};

 

static int __init init_module(void) 
{
     int result;

     printk(KERN_INFO "my_pci_driver built on %s, %s\n",__DATE__,__TIME__);

     result = pci_register_driver(&my_pci_driver ); //注冊設備驅動
     if(result)
          return result;

     return 0;
}

 

卸載設備模塊:

static void __devexit my_pci_remove(struct pci_dev *pci_dev)
{
     struct device_private *private;
     private= (struct device_private*)pci_get_drvdata(pci_dev);
    
     printk("FCswitch->irq = %d\n",private->irq);
     

     // register_w32 是封裝的宏,便於直接操作

     // #define register_w32 (reg, val32)     iowrite32 ((val32), device_private->ioaddr + (reg))

     // 這里的作用是關中斷,硬件復位

     register_w32(IntrMask,0x00000001); 
     register_w32(Reg_reset,0x00000001);
    
     // 移除動態創建的設備號和設備
     device_destroy(device_class, device->my_dev);
     class_destroy(device_class);
     

     cdev_del(&private->my_cdev);
     unregister_chrdev_region(priv->my_dev,1);
    
     //清理用於映射到用戶空間的內存頁面
     for(private->virts_addr = (unsigned long)private->rx_buf_virts;private->virts_addr < (unsigned long)private->rx_buf_virts + BUF_SIZE;private->virts_addr += PAGE_SIZE)
     {
          ClearPageReserved(virt_to_page(FCswitch->virts_addr));
     }
     ...

     // 釋放分配的內存空間
     pci_free_consistent(private->my_pdev, BUF_SIZE, private->rx_buf_virts, private->rx_dma_addrp);
     ...    


     free_irq(private->irq, private);


     iounmap(private->ioaddr);
     pci_release_regions(pci_dev);
     kfree(private);
    
     pci_set_drvdata(pci_dev,NULL);
     pci_disable_device(pci_dev);
}

// 總之模塊卸載函數的職責就是釋放一切分配過的資源,根據自己代碼的需要進行具體的操作

 

PCI設備的探測(probe):

static int __devinit pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
{
     unsigned long mmio_start;
     unsigned long mmio_end;
     unsigned long mmio_flags;
     unsigned long mmio_len;
     void __iomem *ioaddr1=NULL;
     struct device_private *private;
     int result;
     printk("probe function is running\n");

     /* 啟動PCI設備 */
     if(pci_enable_device(pci_dev))
     {
          printk(KERN_ERR "%s:cannot enable device\n",pci_name(pci_dev));
          return -ENODEV;
     }
     printk( "enable device\n");
     /* 在內核空間中動態申請內存 */
     if((private= kmalloc(sizeof(struct device_private), GFP_KERNEL)) == NULL)
     {
          printk(KERN_ERR "pci_demo: out of memory\n");
          return -ENOMEM;
     }
     memset(private, 0, sizeof(*private));

     private->my_pdev = pci_dev;

     mmio_start = pci_resource_start(pci_dev, 0);
     mmio_end = pci_resource_end(pci_dev, 0);
     mmio_flags = pci_resource_flags(pci_dev, 0);
     mmio_len = pci_resource_len(pci_dev, 0);
     printk("mmio_start is 0x%0x\n",(unsigned int)mmio_start);
     printk("mmio_len is 0x%0x\n",(unsigned int)mmio_len);
     if(!(mmio_flags & IORESOURCE_MEM))
     {
          printk(KERN_ERR "cannot find proper PCI device base address, aborting.\n");
          result = -ENODEV;
          goto err_out;
     }
    

     /* 對PCI區進行標記 ,標記該區域已經分配出去*/
     result = pci_request_regions(pci_dev, DEVICE_NAME);
     if(result)
          goto err_out;

    
     /* 設置成總線主DMA模式 */
     pci_set_master(pci_dev);
    
     /*ioremap 重映射一個物理地址范圍到處理器的虛擬地址空間, 使它對內核可用.*/

     ioaddr1 = ioremap(mmio_start, mmio_len);
     if(ioaddr1 == NULL)
     {
          printk(KERN_ERR "%s:cannot remap mmio, aborting\n",pci_name(pci_dev));
          result = -EIO;
          goto err_out;
     }
     printk("ioaddr1 =  0x%0x\n",(unsigned int)ioaddr1);

    

     private->ioaddr = ioaddr1;
     private->mmio_addr = mmio_start;
     private->regs_len = mmio_len;
     private->irq = pci_dev->irq;
     printk("irq is %d\n",pci_dev->irq);


     /* 初始化自旋鎖 */
     spin_lock_init(&private->lock_send);
     spin_lock_init(&private->lock_rev);
    

     if(my_register_chrdev(private)) //注:這里的注冊字符設備,類似於前面的文章中介紹過的動態創建設備號和動態生成設備結點
     {
          printk("chrdev register fail\n");
          goto err_out;
     }

     //下面這兩個函數根據具體的硬件來處理,主要就是內存分配、對硬件進行初始化設置等
     device_init_buf(xx_device);//這個函數主要進行內存分配,內存映射,獲取中斷
     device_hw_start(xx_device);//這個函數主要是往寄存器中寫一些值,復位硬件,開中斷,打開DMA等
    

     //把設備指針地址放入PCI設備中的設備指針中,便於后面調用pci_get_drvdata

     pci_set_drvdata(pci_dev, FCswitch);  
      return 0;
err_out:
     printk("error process\n");
      resource_cleanup_dev(FCswitch); //如果出現任何問題,釋放已經分配了的資源
     return result;
}

// probe函數的作用就是啟動pci設備,讀取配置空間信息,進行相應的初始化

 

中斷處理:

//中斷處理,主要就是讀取中斷寄存器,然后調用中斷處理函數來處理中斷的下半部分,一般通過tasklet或者workqueue來實現

注意:由於使用request_irq 獲得的中斷是共享中斷,因此在中斷處理函數的上半部需要區分是不是該設備發出的中斷,這就需要讀取中斷狀態寄存器的值來判斷,如果不是該設備發起的中斷則 返回 IRQ_NONE

static irqreturn_t device_interrupt(int irq, void *dev_id)

{

     ...

   if( READ(IntMask) == 0x00000001)
   {
      return IRQ_NONE;
   }    WRITE(IntMask,0x00000001);
tasklet_schedule(
&my_tasklet); // 需要先申明tasklet 並關聯處理函數 ... return IRQ_HANDLED; } // 聲明tasklet static void my_tasklet_process(unsigned long unused); DECLARE_TASKLET(my_tasklet, my_tasklet_process, (unsigned long)&private);//第三個參數為傳遞給my_tasklet_process 函數的參數 

 

設備驅動的接口:

 static struct file_operations device_fops = {

     owner:     THIS_MODULE,
     open:       device_open, //打開設備
     ioctl:        device_ioctl,  //設備控制操作
     mmap:     device_mmap,//內存重映射操作
     release:    device_release,// 釋放設備
};

 

打開設備:

open 方法提供給驅動來做任何的初始化來准備后續的操作. open 方法的原型是:

int (*open)(struct inode *inode, struct file *filp);
inode 參數有我們需要的信息,以它的 i_cdev 成員的形式, 里面包含我們之前建立的cdev 結構. 唯一的問題是通常我們不想要 cdev 結構本身, 我們需要的是包含 cdev 結構的 device_private 結構. 

static int device_open(struct inode *inode, struct file *filp)
{
     struct device_private *private;
     private= container_of(inode->i_cdev, struct device_private, my_cdev);
     filp->private_data = private;

     private->open_flag++;
     try_module_get(THIS_MODULE);
     ...
     return 0;
} 

釋放設備:

release 方法的角色是 open 的反面,設備方法應當進行下面的任務: 

•  釋放 open 分配在 filp->private_data 中的任何東西
•  在最后的 close 關閉設備 

 static int FCswitch_release(struct inode *inode,struct file *filp)

{
     struct device_private *private= filp->private_data;
     private->open_flag--;
 
     module_put(THIS_MODULE);

     printk("pci device close success\n");

     return 0;
}

 

設備控制操作:

PCI設備驅動程序可以通過device_fops 結構中的函數device_ioctl( ),向應用程序提供對硬件進行控制的接口。例如,通過它可以從I/O寄存器里讀取一個數據,並傳送到用戶空間里。

static int device_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
     int retval = 0;
     struct device_private *FCswitch = filp->private_data;
     
     switch (cmd)
     {

          case DMA_EN://DMA使能
               device_w32(Dma_wr_en, arg);
               break;

          ...

          default:
               retval = -EINVAL;
     }
     return retval;
}

 

內存映射:

static int device_mmap(struct file *filp, struct vm_area_struct *vma)
{
     int ret;
     struct device_private *private = filp->private_data;
     vma->vm_page_prot = PAGE_SHARED;//訪問權限
     vma->vm_pgoff = virt_to_phys(FCswitch->rx_buf_virts) >> PAGE_SHIFT;//偏移(頁幀號)

     ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, (unsigned long)(vma->vm_end-vma->vm_start), vma->vm_page_prot);
     if(ret!=0)
          return -EAGAIN;

     return 0;
}

 

對 remap_pfn_range()函數的說明:

remap_pfn_range()函數的原型:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);

   該函數的功能是創建頁表。其中參數vma是內核根據用戶的請求自己填寫的,而參數addr表示內存映射開始處的虛擬地址,因此,該函數為addr~addr+size之間的虛擬地址構造頁表。

   另外,pfn(Page Fram Number)是虛擬地址應該映射到的物理地址的頁面號,實際上就是物理地址右移PAGE_SHIFT位。如果PAGE_SHIFT為4kb,則 PAGE_SHIFT為12,因為PAGE_SHIFT等於1<<PAGE_SHIFT。最后一個參數prot是新頁所要求的保護屬性。
    在驅動程序中,一般能使用remap_pfn_range()映射內存中的保留頁(如X86系統中的640KB~1MB區域)和設備I/O內存。因此,如 果想把kmalloc()申請的內存映射到用戶空間,則可以通過SetPageReserved把相應的內存設置為保留后就可以。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM