(海思LonWorks技術(shù)粉絲供稿)
引言
在現(xiàn)場總線產(chǎn)品開發(fā)及系統(tǒng)建設(shè)中,各類新設(shè)備及新的接口規(guī)范等,使得操作系統(tǒng)的設(shè)備驅(qū)動程序的開發(fā)工作層出不窮。在基于嵌入現(xiàn)場總線控制器的開發(fā)中,將遇到LonWorks設(shè)備的驅(qū)動程序問題。對驅(qū)動程序?qū)崿F(xiàn)機制進行研究,對開發(fā)LonWorks現(xiàn)場總線設(shè)備的驅(qū)動程序十分必要。
一、LonWorks技術(shù)簡介
現(xiàn)場總線是一類工業(yè)數(shù)據(jù)總線,是連接智能現(xiàn)場設(shè)備和自動化系統(tǒng)的高可靠的數(shù)字式、雙向傳輸?shù)耐ㄐ偶夹g(shù),可方便地構(gòu)成全數(shù)字化的分布式現(xiàn)場控制網(wǎng)絡(luò)。在各種現(xiàn)場總線中,LonWorks總線技術(shù)以其在技術(shù)先進性、可靠性、開放性、拓?fù)浣Y(jié)構(gòu)靈活性等方面獨特的優(yōu)勢,為分布式監(jiān)控系統(tǒng)提供了理想的實現(xiàn)手段。特別適合于建筑的樓宇自動化系統(tǒng)。
LON網(wǎng)絡(luò)接口卡是上位機與LonWorks網(wǎng)絡(luò)的接口適配器,使上位機能夠完成與LonWorks節(jié)點之間的數(shù)據(jù)通信。
(一) LonWorks網(wǎng)卡的硬件構(gòu)成
了解LonWorks網(wǎng)卡的工作原理,對編寫驅(qū)動程序是必要的。
圖1是LonWorks網(wǎng)卡的硬件原理框圖。
在LonWorks網(wǎng)卡的設(shè)計中,使用可編程邏輯陣列(CPLD)來實現(xiàn)與ISA總線的接口邏輯,只用一個芯片就完成了所有功能,大大簡化了網(wǎng)卡的電路。
(二)LonWorks網(wǎng)卡的工作原理
計算機與微控制器之間數(shù)據(jù)交互的流程圖如圖2、3所示,完成計算機與微控制器之間讀寫數(shù)據(jù)、置標(biāo)志位和清除標(biāo)志位的功能。CPLD為內(nèi)部實現(xiàn)了存儲數(shù)據(jù)和標(biāo)志位的寄存器。
二、LonWorks網(wǎng)卡設(shè)備驅(qū)動實現(xiàn)
在Linux平臺上開發(fā)和設(shè)計LonWorks網(wǎng)卡的軟件包含應(yīng)用程序和設(shè)備驅(qū)動程序兩部分。本文主要討論的是設(shè)備驅(qū)動程序部分。
在Linux平臺上實現(xiàn)對硬件的驅(qū)動支持采用了如下工作方式:使用Linux內(nèi)核中提供的機制來實現(xiàn)。
(一) Linux的可加載模塊機制
Linux內(nèi)核提供了兩種機制來開發(fā)設(shè)備驅(qū)動程序:一種是直接把驅(qū)動程序鏈接到內(nèi)核中;另一種則是通過稱為Linux可加載模塊的機制來開發(fā)可動態(tài)加載和卸載的驅(qū)動模塊。而第一種方式可以在后一種方式成功后,采用與內(nèi)核一起提供的配置工具和接口來完成。
Linux作為單核結(jié)構(gòu)其效率比較高,但是系統(tǒng)靈活性不足,為了平衡這兩者的關(guān)系,它提供了可動態(tài)加載機制。利用這種機制我們可以開發(fā)Linux內(nèi)核模塊,并且可以動態(tài)的對它加載和卸載。Linux下的設(shè)備驅(qū)動程序一般都支持這種方式,且模塊被加載到內(nèi)核后,它就可以任意的利用核心提供的各種資源和服務(wù)了。為了讓模塊利用核心提供的資源,Linux內(nèi)核維護了一張所有內(nèi)核資源的符號表(在接下來的部分我們稱它為內(nèi)核資源符號表),用于在模塊載入時解決對相應(yīng)資源的引用問題。并且,Linux允許模塊的堆棧操作,由此一個模塊可以使用其他模塊所提供的資源。也就是說:一個模塊對另一個模塊的資源的使用與其對內(nèi)核資源的使用非常相似,不同的只是這些服務(wù)的資源從屬于另一個模塊而已。每當(dāng)一個模塊被加載Linux就會有一個修改內(nèi)核資源符號表的過程,將該模塊所提供的服務(wù)和資源加入進去,這樣另一個模塊載入時,如果需要就可以引用這個模塊的資源了。而卸載一個模塊時,就要知道當(dāng)前模塊是否正在被使用。如果沒有被使用,在卸載時要能夠通知該模塊它將被卸載,以便由它自己釋放已被它占用的系統(tǒng)資源。然后,Linux還要從內(nèi)核資源符號表中刪除所有該模塊提供的資源和服務(wù)。
從上面的原理分析可知,內(nèi)核模塊編寫時應(yīng)該具有兩個主要的接口函數(shù):init_module()用于在模塊加載時由加載模塊的工具調(diào)用,以便于注冊一些必要的服務(wù)和申請一些資源。cleanup_module()用于在模塊卸載時由刪除模塊的工具來調(diào)用,清除掉由init_module()所做的工作,從而使內(nèi)核模塊可以安全的卸載。其中對init_module()調(diào)用的一種工具是在根用戶執(zhí)行insmod命令來加載模塊時執(zhí)行。而對于cleanup_module()的調(diào)用是在根用戶使用rmmod命令來卸載模塊時執(zhí)行。
(二) Linux下設(shè)備驅(qū)動程序
系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核和應(yīng)用程序之間的接口, .aspx" title="設(shè)備" style="text-decoration:underline;color:blue">設(shè)備驅(qū)動程序是操作系統(tǒng)內(nèi)核和機器硬件之間的接口。設(shè)備驅(qū)動程序為應(yīng)用程序屏蔽了硬件的細(xì)節(jié),這樣在應(yīng)用程序看來,硬件設(shè)備只是一個設(shè)備文件,可以通過相應(yīng)的系統(tǒng)調(diào)用象操作普通文件一樣對硬件設(shè)備進行操作。
(1) Linux設(shè)備分類
Linux系統(tǒng)的設(shè)備分為字符設(shè)備(char device),塊設(shè)備(block device)和網(wǎng)絡(luò)設(shè)備(network device)三種。字符設(shè)備是指存取時沒有緩存的設(shè)備,如系統(tǒng)的串口設(shè)備/dev/cua0, /dev/cual。塊設(shè)備的讀寫則都有緩存來支持,只能以塊為單位進行讀寫,并且塊設(shè)備必須能夠隨機存取(random access),即不管塊處于設(shè)備的什么地方都可以對它進行讀寫,字符設(shè)備則沒有這個要求。塊設(shè)備主要包括硬盤軟盤設(shè)備,CD-ROM等。網(wǎng)絡(luò)設(shè)備在Linux里做專門的處理。Linux的網(wǎng)絡(luò)系統(tǒng)主要是基于BSD unix的socket機制。
(2) 設(shè)備標(biāo)識方式
Linux設(shè)備由一個主設(shè)備號和一個次設(shè)備號標(biāo)識。主設(shè)備號唯一標(biāo)識了設(shè)備類型,即設(shè)備驅(qū)動程序類型,它是塊設(shè)備表或字符設(shè)備表中相應(yīng)表項的索引。次設(shè)備號僅由設(shè)備驅(qū)動程序解釋,一般用于識別在若干可能的硬件設(shè)備中,I/O請求所涉及到的那個設(shè)備。值得一提的是次設(shè)備號還可以被分成幾個部分用來區(qū)分子設(shè)備驅(qū)動程序和具體的設(shè)備。
(3) Linux設(shè)備驅(qū)動程序組成部分
Linux設(shè)備驅(qū)動程序可以分為三個主要組成部分:
●自動配置和初始化子程序。負(fù)責(zé)檢測所要驅(qū)動的硬件設(shè)備是否存在和是否能正常工作。如果該設(shè)備正常,則對這個設(shè)備及其相關(guān)的、設(shè)備驅(qū)動程序需要的軟硬件進行初始化。
●服務(wù)于I/O請求的子程序。它們主要是對file_operations結(jié)構(gòu)的各個入口點的實現(xiàn)。這部分的實現(xiàn)支持了文件系統(tǒng)的調(diào)用(如open,close,
read等等)。
●中斷服務(wù)子程序。在Linux系統(tǒng)中,并不是直接從中斷向量表中調(diào)用設(shè)備驅(qū)動程序的中斷服務(wù)子程序,而是由Linux系統(tǒng)來接收硬件中斷,再由系統(tǒng)來調(diào)用中斷服務(wù)子程序。
但是,這三個部分不是必須在每個驅(qū)動程序中必須具有的。
(三) LonWorks網(wǎng)卡驅(qū)動程序
根據(jù)Linux的設(shè)備管理以及設(shè)備驅(qū)動程序?qū)崿F(xiàn)方法,LonWorks節(jié)點設(shè)備驅(qū)動程序即可進行編寫實現(xiàn),并對實現(xiàn)中的一些關(guān)鍵問題進行探討。
(1) LonWorks現(xiàn)場總線網(wǎng)卡驅(qū)動程序
在驅(qū)動程序設(shè)計和開發(fā)中,一定要注意機制(Mechanism)與策略(Policy)的分離。所謂的機制是指驅(qū)動程序提供的接口應(yīng)該忠實地反映設(shè)備的原始功能,而與應(yīng)用無關(guān)。而策略是指一旦這個設(shè)備驅(qū)動程序為設(shè)備機制提供了相應(yīng)的軟件接口,應(yīng)用程序開發(fā)人員就能按照特定的方式使用機制接口。可以說,在內(nèi)核驅(qū)動程序開發(fā)過程中,所設(shè)計的數(shù)據(jù)結(jié)構(gòu),以及確定的接口命令都是為以后的應(yīng)用策略提供的一種機制。而如前所述,這種機制在Unix類系統(tǒng)內(nèi)部是通過一組固定的入口點來提供的。由于我們要開發(fā)的設(shè)備驅(qū)動程序是一個字符型的設(shè)備,所以接下來我們首先分析字符型設(shè)備驅(qū)動程序中常用的入口點:
● open入口點
打開設(shè)備準(zhǔn)備I/O操作。對字符設(shè)備文件進行打開操作,都會調(diào)用設(shè)備的open入口點。open子程序必須對將要進行的I/O操作做好必要的準(zhǔn)備工作,如清除緩沖區(qū)等。如果設(shè)備是獨占的,即同一時刻只能有一個程序訪問此設(shè)備,則open子程序必須設(shè)置一些標(biāo)志以表示設(shè)備處于忙狀態(tài)。
●release入口點
關(guān)閉一個設(shè)備。當(dāng)最后一次使用設(shè)備終結(jié)后,調(diào)用release子程序。獨占設(shè)備必須改變前由open子程序設(shè)置的標(biāo)志,以便設(shè)備可再次被使用。
●read入口點
從設(shè)備上讀數(shù)據(jù)。對于有緩沖區(qū)的I/O操作,一般是從緩沖區(qū)里讀數(shù)據(jù)。對字符設(shè)備文件進行讀操作將調(diào)用read子程序。
●write入口點
往設(shè)備上寫數(shù)據(jù)。對于有緩沖區(qū)的I/O操作,一般是把數(shù)據(jù)寫入緩沖區(qū)里。對字符設(shè)備文件進行寫操作將調(diào)用write子程序。
● ioctl入口點
執(zhí)行讀、寫之外的一些硬件控制操作。
●poll入口點
把對許多非阻塞操作的設(shè)備描述符集合起來,等待事件的發(fā)生,以便于集中檢查,看數(shù)據(jù)是否可從設(shè)備讀取或設(shè)備是否可用于寫數(shù)據(jù),這樣就做到了所謂的多路復(fù)用。
以上入口點構(gòu)成了設(shè)備驅(qū)動程序的三大組成部分中I/O請求的部分,在Linux中它們由file_operations結(jié)構(gòu)來封裝,并不是所有的字符設(shè)備驅(qū)動程序都必須提供以上每一個入口點的實現(xiàn),如果設(shè)備驅(qū)動程序沒有提供上述入口點中的某幾個,系統(tǒng)會用缺省的子程序來代替。
由上面的描述可見,在內(nèi)核設(shè)備驅(qū)動程序的設(shè)計中,相應(yīng)的機制的提供主要是對設(shè)備入口點的選擇和設(shè)計。
針對LonWorks網(wǎng)卡的特點,選擇并實現(xiàn)了五個入口點,即open, release,read,write, ioctl。對于open和release入口點由于設(shè)備特點,只需要控制設(shè)備驅(qū)動模塊在使用時,不被異常釋放即可。接下來將描述以上設(shè)計實現(xiàn)中與Linux內(nèi)核相關(guān)的一些調(diào)用和問題。
(2) 對file_operations結(jié)構(gòu)的初始化file_operations結(jié)構(gòu)是Linux操作系統(tǒng)中用于實現(xiàn)驅(qū)動程序的最重要的數(shù)據(jù)結(jié)構(gòu),前面提到過,它對Linux提供I/O請求的子程序的一系列入口點進行了封裝。該結(jié)構(gòu)貫穿在整個驅(qū)動程序中,故在文件作用域內(nèi)定義了它的一個變量,并對本程序中用到的入口點做了初始化,其代碼如下:
struct file_operations lmdev_fops= {
NULL,
lmdev_read,
//把實現(xiàn)的lmdev_read函數(shù)指針賦給read入口點。
lmdev_write,
//把實現(xiàn)的lmdev_write函數(shù)指針賦給write入口點。
NULL,
NULL,
lmdev_ioctl,
//把實現(xiàn)的lmdev_ioctl函數(shù)指針賦給ioctl入口點。
NULL,
lmdev_open,
//把實現(xiàn)的lmdev_ open函數(shù)指針賦給open入口點。
lmdev_release,
//把實現(xiàn)的lmdev_release函數(shù)指針賦給release入口點。
NULL,
NULL,
NULL,
NULL,
};
對于lmdev-*函數(shù)的實現(xiàn)方法,我們將在后面做詳細(xì)的討論。
(3) 模塊初始化與模塊卸載
● 9;color:blue">LonWorks網(wǎng)卡驅(qū)動模塊初始化,通過對init_module的實現(xiàn)來完成以下幾個任務(wù)。以字符設(shè)備類型向系統(tǒng)注冊LonWorks現(xiàn)場總線設(shè)備卡,同時動態(tài)獲得其設(shè)備號。通過調(diào)用下面這個函數(shù)int
register_ chrdev(unsigned int major, const char*name,struct file_operations
*fops)來實現(xiàn)。
這里我們使major參數(shù)為0,這樣系統(tǒng)就會動態(tài)的分配并返回主設(shè)備號。name參數(shù)是用于標(biāo)識設(shè)備的字符串。file_operatons傳入的是如前所述的lmdev_fops。然后,向系統(tǒng)申請LonWorks網(wǎng)卡的I/O端口地址。根據(jù)該卡上的跳線得到的I/O地址,調(diào)用系統(tǒng)提供的宏:check_region(start,n)//檢查端口地址范圍start到start+n-1是否可用,是則返回0,否則返回1。request_region(start,n,name)//用于申請通過以上函數(shù)檢查的地址范圍。接下來,做一些必要的系統(tǒng)日志,根據(jù)各種條件用printk向系統(tǒng)日志緩沖區(qū)寫入不同級別的信息。最后,控制對內(nèi)核資源提供的符號表輸出的符號信息(即在可加載模塊機制部分提到的模塊要注冊的服務(wù))。這里使用EX-PORT_NO_SYMBOLS使得該模塊不輸出任何符號信息。
●LonWorks現(xiàn)場總線網(wǎng)卡模塊卸載需要完成以下幾個任務(wù):
調(diào)用release_region(start,n)宏釋放模塊初始化時申請的I/O端口資源。
調(diào)用int unregister_chrdev(unsigned int major, const char*name);
向系統(tǒng)注銷該字符設(shè)備,本程序中major參數(shù)即前面注冊時動態(tài)獲得的主設(shè)備號,name與注冊時提供的name字符串相同。調(diào)用printk函數(shù),做一些必要的系統(tǒng)日志。
(4) 對file operations結(jié)構(gòu)中入口點的實現(xiàn)
●open和release入口點。
這兩個入口點在本模塊中被賦予的就是前面在介紹file_operations結(jié)構(gòu)時給出的lmdev_open和lmdev_close函數(shù)指針,它們主要通過調(diào)用MOD_INC_USE_COUNT及MOD_DEC_USE_COUNT來進行模塊計數(shù)。用計數(shù)來對LonWorks現(xiàn)場總線設(shè)備驅(qū)動模塊是否正在被使用進行控制,防止模塊正在使用時被意外卸載而導(dǎo)致核心對設(shè)備操作出現(xiàn)異常。
●對read/write入口點的實現(xiàn)
這個入口點在本模塊中被賦予的就是前面在介紹file_operations結(jié)構(gòu)時給出的lmdev_read函數(shù)指針,它是對設(shè)備操作的核心部分,根據(jù)前面描述的算法,它實現(xiàn)了如下幾個功能:
用inb_p宏,訪問硬件的狀態(tài)和數(shù)據(jù)端口,以讀取相應(yīng)的狀態(tài)和數(shù)據(jù)信息。
調(diào)用long_sleep_on_timeout(wait_queue_head_t *q, long timeout)函數(shù)把當(dāng)前進程加入時鐘等待隊列q中,使它等待timeout時間。根據(jù)LonWorks現(xiàn)場總線卡的工作方式來看,這樣做可以減少輪詢時間,大大的提高了效率。
Linux分為核心空間和用戶空間,用戶空間的代碼不能直接訪問核心空間,故需調(diào)用Linux核心提供的copy_to_user(to,from,n)宏,把數(shù)據(jù)從內(nèi)核空間地址from拷貝到用戶空間地址to中。這樣,系統(tǒng)調(diào)用返回后,用戶空間的代碼就可以通過to指針來訪問相應(yīng)的數(shù)據(jù)并進行處理了。這樣核心驅(qū)動模塊部分的程序就完成了。
(5) 編譯內(nèi)核模塊
在程序完成后,用gcc編譯成目標(biāo)文件(不鏈接,生成*.o文件),要做到這一點只需在gcc命令行里加上-c參數(shù)。另外,還要加上-D_KERNEL_ -DMODULE參數(shù)。上述程序可以這么編譯。
root# gcc -c -D-KERNEL_-DMODULE -Wall -02
lmdev.c。其中參數(shù)-Wall的功能是打印附加的警告信息。由于頭文件中的函數(shù)都是聲明為inline的,還必須給編譯器指定-O選項。gcc只有打開優(yōu)化選項后才能擴展內(nèi)嵌函數(shù),不過它能同時接受-g和-O選項,這樣就可以調(diào)試那些內(nèi)嵌函數(shù)的代碼了。優(yōu)化參數(shù)-O有三個級別:Ol,02, 03,它們的優(yōu)化程度不同,優(yōu)化效果03大于02大于Ol。編譯好模塊后的如何加載模塊,在前面已經(jīng)有所描述,這里就不再敘述了。
(四) 應(yīng)用程序開發(fā)
在對以上模塊編譯并加載后,Linux根據(jù)用戶可用mknod命令,利用動態(tài)分配的主設(shè)備號(該設(shè)備號在用戶空間可以從/proc/devices文件中用設(shè)備名獲得)建立相應(yīng)的設(shè)備文件,并對它設(shè)置恰當(dāng)讀寫權(quán)限后,就可以在應(yīng)用程序中,使用Linux的文件系統(tǒng)調(diào)用通過這個設(shè)備文件來操作LonWorks現(xiàn)場總線卡了。這樣做不僅使得應(yīng)用程序編程風(fēng)格更加統(tǒng)一,代碼更具魯棒性,應(yīng)用系統(tǒng)更加安全更易于維護。而且可在核心級來保證關(guān)鍵部分的實時響應(yīng),從而降低了用戶程序開發(fā)的難度。
(本文僅供對LonWorks技術(shù)開發(fā)有興趣者學(xué)習(xí)、參考,不代表本網(wǎng)站同意其觀點及方法)