寫在前面的話:
上一篇,我們將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