摘 要: 以一個具體的PCI設備的驅(qū)動開發(fā)過程為基礎,總結(jié)了與PCI設備驅(qū)動開發(fā)的相關問題,詳細闡述了基本開發(fā)步驟、具體實現(xiàn)、驅(qū)動程序內(nèi)核塊的加載以及用戶進程和驅(qū)動程序的協(xié)同工作問題。
關鍵詞: PCI設備 設備驅(qū)動 中斷
1 Linux 系統(tǒng)下設備驅(qū)動的概念
在Linux操作系統(tǒng)下,系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核和應用程序之間的接口,設備驅(qū)動程序是操作系統(tǒng)內(nèi)核和機器硬件之間的接口[2]。設備驅(qū)動程序為應用程序屏蔽了硬件的細節(jié)。這樣在應用程序看來,硬件設備只是一個文件,即特殊的設備文件。因此應用程序通過特定的設備驅(qū)動程序可以像操作普通文件一樣對具體的硬件設備進行操作。應用程序和設備驅(qū)動的關系如圖1所示。
在Linux操作系統(tǒng)下有二種主要的設備文件類型,一種是字符設備,另一種是塊設備。每種設備文件和實際的硬件相聯(lián)系。字符設備和塊設備的主要區(qū)別是:字符設備在寫請求的同時,實際的硬件I/O操作便發(fā)生了。塊設備則不然,它利用一塊系統(tǒng)內(nèi)存作緩沖區(qū),當用戶進程對設備請求讀/寫時,它首先察看緩沖區(qū)的內(nèi)容。如果緩沖區(qū)的數(shù)據(jù)能滿足用戶的要求,就返回請求的數(shù)據(jù),如果不能,就調(diào)用請求函數(shù)來進行實際的I/O操作。塊設備主要是針對磁盤等慢速設備設計的,以免等待時耗費過多的CPU時間。
就特定的PCI卡,用戶可根據(jù)用途來確定是將其作為塊設備,還是字符設備來處理。本文根據(jù)實際工作中所用到的PCI采集卡的特點,簡要闡述了其驅(qū)動程序開發(fā)過程中需要注意的問題和基本步驟。
2 Linux系統(tǒng)PCI設備驅(qū)動程序的開發(fā)
2.1 基本需求分析
根據(jù)設備不同的用途,可以區(qū)分不同的PCI設備類型。基于這一設備類型,又可以分析出其他一些基本需求。從本文所使用的數(shù)據(jù)采集卡可知,其主要用途是用于采集和控制。根據(jù)工控過程的特點,需要PCI采集卡在每采樣一個數(shù)據(jù)點時,就以中斷的方式交給內(nèi)核緩沖,再由用戶程序適時取出使用。因此,將PCI采集卡作為一個字符設備來處理,并選擇觸發(fā)模式為內(nèi)觸發(fā),數(shù)據(jù)傳送模式為中斷傳送。本文就是基于這樣的需求,按如下所述的步驟和具體實現(xiàn)過程,開發(fā)了其驅(qū)動程序。
2.2 基本步驟
(1)PCI設備文件的建立
既然PCI設備被操作系統(tǒng)當作特殊的文件來看,就要有個文件名。因此,建立一設備文件的名字來代表硬件設備是開發(fā)驅(qū)動的第一步。按照習慣,設備文件都放在系統(tǒng)目錄/dev下。一般在開發(fā)過程中,由于要經(jīng)常查看設備文件的狀態(tài),而在這一目錄下已經(jīng)有很多設備文件,查看起來特別不方便,因此,可以自己在某個地方建立一個文件夾,將該設備文件放在該文件夾下。使用mknod命令可以建立設備特殊文件(注意:只有root賬號的超級用戶才能使用些命令),其格式示例為:
$mknod /subfolders/mydev/PCIdrv c 254 0
也就是用主設備號254(一般在Linux操作系統(tǒng)下設備文件的主設備號不會超過254,所以選用254,以確保該設備號是惟一的)和輔助設備號0在目錄/subfolders/mydev下建立特殊設備文件PCIdrv。
(2)file_operation數(shù)據(jù)結(jié)構(gòu)
設備驅(qū)動程序就是一組能完成特定任務的、在內(nèi)核態(tài)下運行的子函數(shù)的集合。每個設備驅(qū)動程序都有一個被稱作file_operation的數(shù)據(jù)結(jié)構(gòu)來管理、組織這些子函數(shù)。該結(jié)構(gòu)包含了指向驅(qū)動程序內(nèi)部這些子函數(shù)的指針。當系統(tǒng)引導時,內(nèi)核會調(diào)用每個驅(qū)動程序的初始化函數(shù)。它有二個任務要完成:(1)將設備驅(qū)動程序使用的主設備號通知內(nèi)核;(2)初始化函數(shù)將file_operation指針傳送給內(nèi)核。基本結(jié)構(gòu)為:
struct file_operations PCI_fops={NULL,PCIread,
PCIwrite,NULL,NULL,NULL,NULL,
PCIopen,PCIrelease,NULL,NULL};
結(jié)構(gòu)中的每一個非NULL成員的名字都對應著一個系統(tǒng)調(diào)用。用戶進程利用系統(tǒng)調(diào)用在對設備文件進行諸如讀/寫操作時,系統(tǒng)調(diào)用通過設備文件的主設備號找到相應的設備驅(qū)動程序,然后讀取這個數(shù)據(jù)結(jié)構(gòu)相應的函數(shù)指針,接著把控制權(quán)交給該函數(shù)。這是Linux的設備驅(qū)動程序工作的基本流程。例如:當用戶進程執(zhí)行open( )調(diào)用時,open( )執(zhí)行體將根據(jù)open所帶的參數(shù)找到PCI設備驅(qū)動程序,并根據(jù)其相關聯(lián)的PCI_fops數(shù)據(jù)結(jié)構(gòu),找到PCIopen子函數(shù)的入口點,接著就執(zhí)行PCIopen函數(shù)體。
(3)編寫驅(qū)動程序子函數(shù)
file_operation的數(shù)據(jù)結(jié)構(gòu)中所定義的子函數(shù)的集合構(gòu)成了具體的設備驅(qū)動的執(zhí)行體。因此編寫驅(qū)動程序子函數(shù)是開發(fā)過程中最為重要的一步,這些子函數(shù)要根據(jù)具體的需求來設計。工作中用到的PCI采集卡的主要功能函數(shù)有:A/D轉(zhuǎn)換函數(shù)AD_INT_Start、AD_INT_Stop、AD_INT_Data,數(shù)字量采集與輸出函數(shù)DI_Data、DO_Data,D/A轉(zhuǎn)換函數(shù)AO_Data等。這里不予贅述。
2.3 PCI采集卡驅(qū)動程序的具體實現(xiàn)
(1)獲取PCI采集卡的基本配置信息
PCI采集卡的驅(qū)動程序主要是完成對采集卡的寄存器和PCI總線控制器的PCI配置空間的設置和讀取,用以啟動采集卡,并按照一定的方式進行采集、傳送、停止等。若要對這些寄存器進行設置和讀取,就要知道這些寄存器的BaseAddress和偏移值、中斷號等相關配置信息。這是對硬件操作的第一步。Linux操作系統(tǒng)對PCI設備提供了大量的初始化函數(shù)(這一點不同于Dos和Windows)。因此,在系統(tǒng)啟動時,這特定的初始化函數(shù)會被調(diào)用,用來檢測系統(tǒng)中存在的所有的PCI設備,并填充PCI設備的配置空間。因此,在開發(fā)PCI設備驅(qū)動時,只要執(zhí)行相關的系統(tǒng)調(diào)用(由系統(tǒng)提供),就可獲得所需要的PCI設備信息。
下面是幾個常用的內(nèi)核函數(shù):
①pcibios_present( )
②int pcibios_find_device(int device_id,int vendor_id,
int index,int*bus_number,int*device_function)
③read_config_byte,read_config_word,read_config_dword
④write_config_byte,write_config_word,write_config_dword
函數(shù)①的功能是:返回一個布爾值表明所運行的計算機是否具有支持PCI設備的能力;函數(shù)②的功能是:返回設備在總線上的位置及函數(shù)指針bus_number、device_function;函數(shù)③、④的功能是:通過調(diào)用該類函數(shù),設備驅(qū)動程序?qū)崿F(xiàn)寄存器空間的訪問,包括讀和寫。
(2)內(nèi)存操作
設備驅(qū)動的子函數(shù)運行在內(nèi)核態(tài),在設備驅(qū)動程序中動態(tài)開辟和釋放內(nèi)存。用kmalloc或get_free_pages直接申請頁而不是用malloc和free;釋放內(nèi)存用的是kfree或free_pages。kmalloc等函數(shù)返回的是物理地址,而malloc等返回的是線性地址,且kmalloc最大只能開辟128KB。
很多硬件需要一塊比較大的連續(xù)內(nèi)存用作DMA傳送,并且需要一直駐留在內(nèi)存,不能被交換到磁盤文件中去。但是kmalloc最多只能開辟128KB的內(nèi)存,這可以通過犧牲一些系統(tǒng)內(nèi)存的方法來解決。具體做法是:若機器是32MB的內(nèi)存,在lilo.conf的啟動參數(shù)中加上mem=30MB,這樣Linux就認為此機器只有30MB的內(nèi)存,剩下的2MB內(nèi)存在vremap之后就可以為DMA所用。
(3)Linux下的中斷及中斷處理
Linux中的中斷處理程序很有特色。它的一個中斷處理程序可分為上半部(top half)和下半部(bottom half)二個部分。之所以會有上半部和下半部之分,完全是考慮到中斷處理的效率。上半部的功能是“登記中斷”,當一個中斷發(fā)生時,它就把設備驅(qū)動程序中中斷例程的下半部掛到該設備的下半部執(zhí)行隊列中去,然后就等待新的中斷的到來。這樣一來,上半部執(zhí)行的速度就會很快,它就可以接受更多設備產(chǎn)生的中斷。上半部之所以要快,是因為它是完全屏蔽中斷的,如果不執(zhí)行完,其他的中斷就不能被及時處理,只能等到這個中斷處理程序執(zhí)行完畢以后。所以,要盡可能多地對設備產(chǎn)生的中斷進行服務和處理,確保中斷處理程序的速度。
要使用一個中斷,必須先向系統(tǒng)登記。實現(xiàn)系統(tǒng)中斷登記的系統(tǒng)調(diào)用形式如下:
int request_irq(unsigned int irq,void(*handle)(int,void*,struct pt_regs*),unsigned int long flags,const char*device);
其中:irq是要申請的中斷號,handle是中斷處理函數(shù)指針,flags是中斷標識,device是設備名。
如果登記成功,則返回0。這時在/proc/interrupts文件中可以看到自己請求的中斷。
(4)內(nèi)核態(tài)和用戶態(tài)下數(shù)據(jù)交換問題
用戶進程在執(zhí)行特定系統(tǒng)調(diào)用使用設備時,系統(tǒng)就從用戶態(tài)進入內(nèi)核態(tài)下運行,這時用戶進程的環(huán)境仍然可用。但在內(nèi)核緩沖區(qū)和用戶進程緩沖區(qū)間進行數(shù)據(jù)交換時,必須要用內(nèi)核提供的專門函數(shù)。主要有:put_user( ),get_user( ),copy_to_user( ),copy_from_user( )等。
通過這些調(diào)用來使用用戶緩沖時還要進行用戶緩沖讀寫權(quán)限的檢驗,否則調(diào)用數(shù)據(jù)交換函數(shù)時會出錯。檢驗函數(shù)為:int verify_area(int access,void*u_addr,unsigned long size)。
3 將驅(qū)動程序嵌入內(nèi)核
由于驅(qū)動程序是在內(nèi)核下運行,因此,要把編寫的驅(qū)動程序嵌入內(nèi)核。驅(qū)動程序可以按照二種方式編譯,一種是編譯進kernel,另一種是編譯成模塊(modules)。如果編譯進內(nèi)核的話,會增加內(nèi)核的大小,還要改動內(nèi)核的源文件,而且不能動態(tài)卸載,不利于調(diào)試。所以推薦使用模塊方式。這種方式可以用insmod命令來加載模塊和用rmmod來卸載模塊。
在用insmod命令將編譯好的模塊調(diào)入內(nèi)核時,init_module 函數(shù)被調(diào)用。在這里,init_module只做了一件事,就是向系統(tǒng)的字符設備表登記了一個字符設備。register_chrdev需要三個參數(shù):其一是希望獲得的設備號,如果為0,系統(tǒng)將選擇一個沒有被占用的設備號返回;其二是設備文件名;其三是用來登記驅(qū)動程序?qū)嶋H執(zhí)行操作的函數(shù)指針。如果登記成功,則返回設備的主設備號;若不成功,則返回一個負值。
下面是用模塊方法將驅(qū)動程序加載進內(nèi)核時用的主要功能函數(shù)體示例,也就是當執(zhí)行inmod 命令時執(zhí)行的函數(shù)體。
int init_module(void)
{
int result;
result=register_chrdev(254,″PCItest″,&PCI_fops);
if(result<0) {
printk(KERN_INFO ″test:can′t get major number\n″);
return result;
}
if(PCItest==0) PCItest=result;/*dynamic*/
return 0;
}
同樣可以用 rmmod 命令卸載模塊:
void cleanup_module(void)
{
unregister_chrdev(254,″PCItest″);
}
在用rmmod卸載模塊時,cleanup_module函數(shù)被調(diào)用,它釋放字符設備PCItest在系統(tǒng)字符設備表中占有的表項。
4 結(jié)束語
設計Linux設備驅(qū)動程序有一定的模式,遵循這個模式,將會大大減輕設計程序的工作量。本文總結(jié)了工作中對一種PCI采集卡的驅(qū)動開發(fā)過程。同網(wǎng)卡的驅(qū)動相比,PCI采集卡驅(qū)動的開發(fā)是一件相對簡單的工作,但它們同屬于PCI設備,具有類似之處。所以,PCI采集卡的驅(qū)動開發(fā)對設計復雜的網(wǎng)絡驅(qū)動程序是很有幫助的。
參考文獻
1 Rubini A著,LISOLEG譯.Linux設備驅(qū)動程序.北京:中國電力出版社,2000
2 蔡震.Linux系統(tǒng)下USB設備驅(qū)動程序的開發(fā).計算機測量與控制,2003;11(2)
3 李善平,劉文蜂.Linux內(nèi)核2.4版源代碼分析大全.北京:機械工業(yè)出版社,2002
4 王學龍.嵌入式Linux系統(tǒng)設計與應用.北京:清華大學出版社,2001
5 Rusling D A著,朱珂譯.Linux編程白皮書.北京:機械工業(yè)出版社,2000