1. 關鍵資料結構
pci裝置上有三種位址空間:pci的i/o空間、pci的儲存空間和pci的配置空間。cpu可以訪問pci裝置上的所有位址空間,其中i/o空間和儲存空間提供給裝置驅動程式使用,而配置空間則由linux核心中的pci初始化**使用。核心在啟動時負責對所有pci裝置進行初始化,配置好所有的pci裝置,包括中斷號以及i/o基址,並在檔案/proc/pci中列出所有找到的pci裝置,以及這些裝置的引數和屬性。
linux驅動程式通常使用結構(struct)來表示一種裝置,而結構體中的變數則代表某一具體裝置,該變數存放了與該裝置相關的所有資訊。好的驅動程式都應該能驅動多個同種裝置,每個裝置之間用次裝置號進行區分,如果採用結構資料來代表所有能由該驅動程式驅動的裝置,那麼就可以簡單地使用陣列下標來表示次裝置號。
在pci驅動程式中,下面幾個關鍵資料結構起著非常核心的作用:
• pci_driver
這個資料結構在檔案include/linux/pci.h裡,這是linux核心版本2.4之後為新型的pci裝置驅動程式所新增的,其中最主要的是用於識別裝置的id_table結構,以及用於檢測裝置的函式probe( )和解除安裝裝置的函式remove( ):
struct pci_driver ;
• pci_dev
這個資料結構也在檔案include/linux/pci.h裡,它詳細描述了乙個pci裝置幾乎所有的硬體資訊,包括廠商id、裝置id、各種資源等:
struct pci_dev ;
2. 基本框架
在用模組方式實現pci裝置驅動程式時,通常至少要實現以下幾個部分:初始化裝置模組、裝置開啟模組、資料讀寫和控制模組、中斷處理模組、裝置釋放模組、裝置解除安裝模組。下面給出乙個典型的pci裝置驅動程式的基本框架,從中不難體會到這幾個關鍵模組是如何組織起來的。
static struct pci_device_id demo_pci_tbl __initdata = ,
};struct demo_card
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
static struct file_operations demo_fops = ;
static struct pci_driver demo_pci_driver = ;
static int __init demo_init_module (void)
static void __exit demo_cleanup_module (void)
module_init(demo_init_module);
module_exit(demo_cleanup_module);
上面這段**給出了乙個典型的pci裝置驅動程式的框架,是一種相對固定的模式。需要注意的是,同載入和解除安裝模組相關的函式或資料結構都要在前面加上__init、__exit等標誌符,以使同普通函式區分開來。構造出這樣乙個框架之後,接下去的工作就是如何完成框架內的各個功能模組了。
3. 初始化裝置模組
在linux系統下,想要完成對乙個pci裝置的初始化,需要完成以下工作:
• 檢查pci匯流排是否被linux核心支援;
• 檢查裝置是否插在匯流排插槽上,如果在的話則儲存它所占用的插槽的位置等資訊。
• 讀出配置頭中的資訊提供給驅動程式使用。
當linux核心啟動並完成對所有pci裝置進行掃瞄、登入和分配資源等初始化操作的同時,會建立起系統中所有pci裝置的拓撲結構,此後當pci驅動程式需要對裝置進行初始化時,一般都會呼叫如下的**:
static int __init demo_init_module (void)
return 0;
}驅動程式首先呼叫函式pci_present( )檢查pci匯流排是否已經被linux核心支援,如果系統支援pci匯流排結構,這個函式的返回值為0,如果驅動程式在呼叫這個函式時得到了乙個非0的返回值,那麼驅動程式就必須得中止自己的任務了。在2.4以前的核心中,需要手工呼叫pci_find_device( )函式來查詢pci裝置,但在2.4以後更好的辦法是呼叫pci_register_driver( )函式來註冊pci裝置的驅動程式,此時需要提供乙個pci_driver結構,在該結構中給出的probe探測例程將負責完成對硬體的檢測工作。
static int __init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
if ((card = kmalloc(sizeof(struct demo_card), gfp_kernel)) == null)
memset(card, 0, sizeof(*card));
card->iobase = pci_resource_start (pci_dev, 1);
card->pci_dev = pci_dev;
card->pci_id = pci_id->device;
card->irq = pci_dev->irq;
card->next = devs;
card->magic = demo_card_magic;
pci_set_master(pci_dev);
request_region(card->iobase, 64, card_names[pci_id->driver_data]);
return 0;
}4. 開啟裝置模組
在這個模組裡主要實現申請中斷、檢查讀寫模式以及申請對裝置的控制權等。在申請控制權的時候,非阻塞方式遇忙返回,否則程序主動接受排程,進入睡眠狀態,等待其它程序釋放對裝置的控制權。
static int demo_open(struct inode *inode, struct file *file)
if(file->f_mode & fmode_write)
down(&card->open_sem);
while(card->open_mode & file->f_mode) else }}
5. 資料讀寫和控制資訊模組
pci裝置驅動程式可以通過demo_fops 結構中的函式demo_ioctl( ),向應用程式提供對硬體進行控制的介面。例如,通過它可以從i/o暫存器裡讀取乙個資料,併發送到使用者空間裡:
static int demo_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
}事實上,在demo_fops裡還可以實現諸如demo_read( )、demo_mmap( )等操作,linux核心原始碼中的driver目錄裡提供了許多裝置驅動程式的源**,找那裡可以找到類似的例子。在對資源的訪問方式上,除了有i/o指令以外,還有對外設i/o記憶體的訪問。對這些記憶體的操作一方面可以通過把i/o記憶體重新對映後作為普通記憶體進行操作,另一方面也可以通過匯流排主dma(bus master dma)的方式讓裝置把資料通過dma傳送到系統記憶體中。
6. 中斷處理模組
pc的中斷資源比較有限,只有0~15的中斷號,因此大部分外部裝置都是以共享的形式申請中斷號的。當中斷發生的時候,中斷處理程式首先負責對中斷進行識別,然後再做進一步的處理。
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
outl(status & int_mask, card->iobase + glob_sta);
spin_unlock(&card->lock);
}7. 釋放裝置模組
釋放裝置模組主要負責釋放對裝置的控制權,釋放占用的記憶體和中斷等,所做的事情正好與開啟裝置模組相反:
static int demo_release(struct inode *inode, struct file *file)
8. 解除安裝裝置模組
解除安裝裝置模組與初始化裝置模組是相對應的,實現起來相對比較簡單,主要是呼叫函式pci_unregister_driver( )從linux核心中登出裝置驅動程式:
static void __exit demo_cleanup_module (void)
Linux下的PCI驅動程式設計
pci裝置上有三種位址空間 pci的i o空間 pci的儲存空間和pci的配置空間。cpu可以訪問pci裝置上的所有位址空間,其中i o空間和儲存空間提供給裝置驅動程式使用,而配置空間則由linux核心中的pci初始化 使用。核心在啟動時負責對所有pci裝置進行初始化,配置好所有的pci裝置,包括中...
linux驅動子系統 PCI
pci子系統的關鍵概念包括 pci裝置 pci插槽 pci域 pci匯流排 pci橋 熱插拔控制器 pci裝置探測流程 linux啟動過程中會呼叫pci核心的探測函式進行pci裝置的探測,並建立起相關的拓撲關係以及記錄每個探測到的裝置的資訊 探測採用遞迴的方式從根控制器開始逐級往下探測,遇到橋控制器...
Linux驅動學習 初識PCI驅動 一
pci是什麼 pci peripheral component interconnect,外圍裝置互 線。是一種廣泛採用的匯流排標準,它提供了許多優於其它匯流排標準 如eisa 的新特性,目前已經成為計算機系統中應用最為廣泛,並且最為通用的匯流排標準。對於pci的一些具體介紹可以參考 pci具體介紹...