Linux驅動學習--初識PCI驅動(二)

寫在前面的話:

上一篇,我們將PCI驅動的基礎知識進行一個簡單的梳理。當然,這是不夠的,因此,接下來,我們進行深一步的探究。


 我們從下面幾個方面來講述:

一,初始化設備模塊

  Linux內核啓動並完成對所有PCI設備進行掃描、登錄和分配資源等初始化操作的同時,會建立起系統中所有PCI設備的拓撲結構,此後當PCI驅動程序需要對設備進行初始化時,一般都會調用如下的代碼:

複製代碼
 static int __init example_init_module (void)   
 {
    /* 註冊硬件驅動程序 */
         if(!pci_register_driver(&example_pci_driver)){
         pci_unregister_driver(&example_pci_driver);
         return-ENODEV;
         }
         /* ... */
        
         return 0;
 }
複製代碼

 從上面的省略號可以看出,這僅僅只是核心的一部分,其他的東西,就要看你具體的應用是在什麼地方。

僅僅使用上面的init函數是不夠的,因爲此時你並不知道你的設備是什麼樣的,是不是都已經準備好了,因此,還有更重要的一步---probe。探測完成對硬件的檢測工作。

我們先將相關代碼進行列示:

複製代碼
 static int __init example_probe(struct pci_dev *pci_dev,conststruct pci_device_id *pci_id)    
{
         struct example_pci *my_pci;
         /* 啓動PCI設備 */
         if(pci_enable_device(pci_dev))
         return-EIO;
         /* 設備DMA標識 */
         if(pci_set_dma_mask(pci_dev, EXAMPLE_DMA_MASK))
         return-ENODEV;
         /* 在內核空間中動態申請內存 */
         if((my_pci = kmalloc(sizeof(struct example_pci), GFP_KERNEL))== NULL){
         printk(KERN_ERR "example_pci: out of memory\n");
         return-ENOMEM;
         }
         memset(my_pci,0,sizeof(*my_pci));
         /* 讀取PCI配置信息 */
         my_pci->iobase = pci_resource_start(pci_dev,1);
         my_pci->pci_dev = pci_dev;
         my_pci->pci_id = pci_id->device;
         my_pci->irq = pci_dev->irq;
         my_pci->next= devs;
         my_pci->magic = EXAMPLE_MAGIC;
         /* 設置成總線主DMA模式 */
         pci_set_master(pci_dev);
         /* 申請I/O資源 */
         request_region(my_pci->iobase,64,my_pci_names[pci_id->driver_data]);
         return 0;
}
複製代碼

 

整個程序的思路很清晰,並不需要去太多的講解,只是對裏面的一些函數需要進行一下講解.

1.pci_enable_device

激活PCI設備,在驅動程序可以訪問PCI設備的任何設備資源之前(I/O區域或者中斷),驅動程序必須調用該函數:

   int pci_enable_device(struct pci_dev *dev);                       /*driver/pci/pci.c*/

 該函數實際的激活設備。它把設備喚醒,在某些情況下還指派它的中斷線和I/O區域。

2.訪問PCI地址空間

在驅動程序檢測到設備之後,它通常需要讀取或寫入三個地址空間:內存,端口和配置。對驅動程序來說,對配置空間的訪問至關重要,因爲這是它找到設備映射到內存和I/O空間的什麼位置的唯一途徑。

因而,首先來看看配置空間的訪問:

Linux內核爲我們想的很周到,在內核中就已經提供了訪問配置空間的標準接口,我們只要去直接調用就好了。

對於驅動程序而言,可通過8位,16位,32位的數據傳輸訪問配置空間。相關函數定義在<linux/pci.h>中:

    int pci_read_config_byte(conststruct pci_dev *dev,intwhere, u8 *val);/*8位,讀入一個字節*/
     
    int pci_read_config_word(conststruct pci_dev *dev,intwhere, u16 *val);/*16位,讀入兩個字節*/
     
    int pci_read_config_dword(conststruct pci_dev *dev,intwhere, u32 *val);/*32位,讀入四個字節*/

      const struct pci_dev *dev:由dev標識的設備配置空間;

      int where:從配置空間起始位置計算的字節偏移量;

      u8/u16/u32 *val:從配置空間獲得的值通過val指針返回;

      函數本身返回的值是錯誤碼。

注意:word和dword函數會將讀取到的little-endian值轉換成處理器固有的字節序。我們自己無需處理字節序。

上面的是讀的情況,寫的情況也是類似的<linux/pci.h>:

    int pci_write_config_byte(conststruct pci_dev *dev,intwhere, u8 *val);/*8位,寫入一個字節*/
     
    int pci_write_config_word(conststruct pci_dev *dev,intwhere, u16 *val);/*16位,寫入兩個字節*/
     
    int pci_write_config_dword(conststruct pci_dev *dev,intwhere, u32 *val);/*32位,寫入四個字節*/

 因此,我們可以利用上面的函數讀取和修改設備的信息。

講完配置空間,接下來嘮叨一下I/O和內存空間。

一個PCI設備可實現多達6個I/O地址區域,每個區域既可以使內存也可以是I/O地址。在內核中PCI設備的I/O區域已經被集成到通用資源管理器。因此,我們無需訪問配置變量來了解設備被映射到內存或者I/O空間的何處。獲得區域信息的首選接口是下面的宏定義:

  #define pci_resource_start(dev, bar)((dev)->resource[(bar)].start)

 該宏返回六個PCI I/O區域之一的首地址(內存地址或者I/O端口號).該區域由整數的bar(base address register,基地址寄存器)指定,bar取值爲0到5。

    #define pci_resource_end(dev, bar)((dev)->resource[(bar)].end)

 該宏返回第bar個I/O區域的首地址。注意這是最後一個可用的地址,而不是該區域之後的第一個地址。

    #define pci_resource_flags(dev, bar)((dev)->resource[(bar)].flags)

 該宏返回和該資源相關聯的標誌。

資源標誌用來定義單個資源的特性,對與PCI I/O區域相關的PCI資源,該信息從基地址寄存器中獲得,但對於和PCI無關的資源,它可能來自其他地方。所有資源標誌定義在<linux/ioport.h>。

二,打開設備模塊

複製代碼
static int example_open(struct inode *inode, struct file *file)    
{
/* 申請中斷,註冊中斷處理程序 */ request_irq(my_pci->irq, &example_interrupt, SA_SHIRQ, my_pci_names[pci_id->driver_data], my_pci)) ; /* 檢查讀寫模式 */ if(file->f_mode & FMODE_READ) { /* ... */ } if(file->f_mode & FMODE_WRITE) { /* ... */ } /* 申請對設備的控制權 */ down(&my_pci->open_sem); while(my_pci->open_mode & file->f_mode) { if (file->f_flags & O_NONBLOCK) { /* NONBLOCK模式,返回-EBUSY */ up(&my_pci->open_sem); return -EBUSY; } else { /* 等待調度,獲得控制權 */ my_pci->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE); up(&my_pci->open_sem); /* 設備打開計數增1 */ MOD_INC_USE_COUNT; /* ... */ } } }
複製代碼

 在這個模塊裏主要實現申請中斷、檢查讀寫模式以及申請對設備的控制權等。在申請控制權的時候,非阻塞方式遇忙返回,否則進程主動接受調度,進入睡眠狀態,等待其它進程釋 放對設備的控制權。

三,數據讀寫和信息控制模塊

複製代碼
static int example_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)   
{
         /* ... */
         
         switch(cmd) {
         case EXAMPLE_RDATA:
             /* 從I/O端口讀取4字節的數據 */
             val = inl(my_pci->iobae + 0x10);
             
            /* 將讀取的數據傳輸到用戶空間 */
             return 0;
         }
         
         /* ... */
}
複製代碼

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

四,中斷模塊

複製代碼
    static void example_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    {
             struct example_pci *my_pci = (struct example_pci *)dev_id;
             u32 status;
             spin_lock(&my_pci->lock);
             /* 中斷 */
             status = inl(my_pci->iobase + GLOB_STA);
             if(!(status & INT_MASK)) 
             {
                 spin_unlock(&my_pci->lock);
                 return; /* not for us */
             }
             /* 告訴設備已經收到中斷 */
             outl(status & INT_MASK, my_pci->iobase + GLOB_STA);
             spin_unlock(&my_pci->lock);
             
             /* 其它進一步的處理 */
    }
複製代碼

 PCI的中斷資源比較有限,只有0~15的中斷號,因此大部分外部設備都是以共享的形式申請中斷號的。當中斷髮生的時候,中斷處理程序首先負責對中斷進行識別,然後再做進一步的處理。

五,釋放設備模塊

複製代碼
static int example_release(struct inode *inode, struct file *file)    
{
     /* ... */
     
     /* 釋放對設備的控制權 */
     my_pci->open_mode &= (FMODE_READ | FMODE_WRITE);
     
     /* 喚醒其它等待獲取控制權的進程 */
     wake_up(&my_pci->open_wait);
     up(&my_pci->open_sem);
     
     /* 釋放中斷 */
     free_irq(my_pci->irq, my_pci);
     
     /* 設備打開計數增1 */
     MOD_DEC_USE_COUNT;
     
     /* ... */
複製代碼

 釋放設備模塊主要負責釋放對設備的控制權,釋放佔用的內存和中斷等,所做的事情正好與打開設備模塊相反。

以上就是我對PCI驅動程序的現階段理解,比較初淺,若有任何不合適的地方,請大家指出。

 參考資料:

Alessandro Rubini,,Linux Device Drivers(3rd Edition) USA:O’Reilly,2005

http://os.chinaunix.net/a2008/0307/978/000000978057.shtml


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章