楊兵見,魏 豐,陳永志
(華中科技大學 自動化學院,武漢 430074)
Linux下的PCIE同步時鐘卡的設備驅動程序開發
楊兵見,魏 豐,陳永志
(華中科技大學 自動化學院,武漢 430074)
為了給系統提供準確的同步時間,向各種自動化裝置(如故障錄波和數據采集等設備)提供高精度同步絕對時標,能夠統一系統中各部分的時間基準,可以在系統發生故障后,為分析故障的情況及開關動作的先后次序提供有力的依據;因此設計了GPS+北斗雙系統接收機制作而成的同步時鐘源,可接收美國GPS(全球定位系統)衛星和我國北斗衛星發出的定位和時間信息;采用了當今計算機最新PCIE總線用來將計算機與時鐘卡進行通訊,獲取時間信息;介紹了Linux下PCIE同步時鐘卡的驅動程序設計,簡要分析了同步時鐘卡的系統框架和工作原理,詳細闡述了Linux下字符設備驅動開發流程,最后完成了Linux下PCIE設備驅動程序的開發和實現;通過實驗驗證了驅動程序的完備性和正確性,能夠準確讀取GPS或者是北斗系統接收機的時間信息。
PCIE總線;設備驅動;Linux;同步時鐘;內核編程
時間同步在航空航天、電力系統、空間技術,民用等領域是一項非常重要的指標,通過時間服務器提供標準可靠的時鐘信號。授時系統就是通過我國的原子鐘和協調世界時間得到精確的信號,服務器得到時鐘信號之后將其發送給它的客戶機,使客戶機的時鐘信號保持一致的精確性和可靠性。
時間這個因素在各種自動化裝置(如電力系統故障檢測,數據采集設備等)起著重要的作用,它能給系統提供統一的時間基準。當系統發生故障時,能夠明確的分析故障所在原因以及系統中開關動作的先后次序,對維護設備和改善設備性能提供了依據。
PCIE總線作為當今計算機主流總線,其驅動程序的開發也備受關注,不管是在Windows還是在Linux下的驅動開發,都得到了廣泛的應用。驅動程序作為軟件與硬件之間的橋梁,獨立于具體的平臺,使其移植更加方便。而且Linux作為開源的操作系統,對于設備驅動的開發人員來說更具有普遍性。
圖1所示為系統框圖,此設備是自主研發的基于PCIE總線的同步時鐘卡,單片機作為系統的控制單元,接收時鐘輸入和控制雙口RAM數據的讀寫。可編程邏輯CPLD用于產生高精度的同步絕對時標,將時間信息存儲于雙口RAM中。PC機通過PCIE總線訪問雙口RAM中的數據,同時設定單片機和PC機訪問雙口RAM的通訊協議,這樣有效防止雙方讀寫雙口RAM數據混亂的情況。CH367芯片作為PCIE總線的接口芯片,EEPROM存儲CH367芯片所需要的配置信息。PC機獲取雙口RAM數據有兩種方式:一種是主動讀取雙口RAM時間數據,另一種是通過外部事件觸發中斷再讀取時間數據。

圖1 系統框圖
2.1 Linux設備驅動概述、分類
Linux將所有的外部設備都看成是一類特殊的文件,我們稱之為“設備文件”[7]。Linux系統調用是應用程序與Linux內核之間的接口,而設備驅動是Linux內核與外部設備之間的接口。驅動程序向應用程序屏蔽了具體的平臺系統,應用程序操作設備文件的讀寫和控制,也就是在相應的操作外部設備的讀寫和控制。
一般而言Linux設備可以分為3類:字符設備、塊設備、網絡設備[1]。
字符設備是以字節為單位逐個進行I/O操作的設備。塊設備利用系統一塊內存作為緩沖區,對設備進行讀寫操作。網絡設備是通過以數據包的形式在網絡媒介上進行數據的發送和接收[1]。
PCIE設備驅動屬于字符設備中的一種。
每一個字符設備或塊設備都在/dev目錄下對應一個設備文件(或稱設備節點)。應用程序通過設備文件來控制驅動程序對外部設備進行相應的操作。
2.2 字符設備驅動
2.2.1 字符設備驅動結構
Linux下的大部分設備都是屬于字符設備,如圖2所示為字符設備、字符設備驅動和用戶空間應用程序的結構圖[1]。

圖2 字符設備驅動的結構
圖2中所示,一個字符設備對應一個實例化的cdev結構體變量名,通過模塊加載函數初始化cdev結構體中的成員并分配設備號,建立cdev結構體與虛擬文件操作結構體file_operations之間的聯系。file_operations定義了字符設備驅動提供給虛擬文件系統的接口函數,通過這些接口,上層的應用程序可以像操作文件那樣操作外部設備。當用戶空間的應用程序進行Linux系統調用時,就會相應調用驅動程序對應的函數。例如應用程序調用open(),write()等函數時,Linux內核就會相應調用xxx_open(),xxx_write()等函數,這樣便完成了對設備的讀寫和控制等操作。當刪除設備時,驅動就會調用模塊卸載函數釋放設備所占用的相應的資源。
2.2.2 字符設備結構體cdev
在Linux內核中,使用cdev結構體描述一個字符設備,cdev結構體的定義在
struct cdev
{
struct kobject kobj; //內嵌的kobject對象
struct module *owner; //所屬模塊
struct file_operations *ops; //文件操作
struct list_head list;
dev_t dev; // 設備號
unsigned int count;
}
首先模塊加載函數初始化cdev結構體,并調用cdev_init(struct cdev *, struct file_operations)函數分配設備號,dev是32位的設備號,其中高12位為主設備號,低20位為次設備號。主設備號代表一個類型的設備驅動,次設備號代表區分該設備中的不同個體。這樣也建立了cdev和file_operations之間的連接。設備號申請之后,應該調用 cdev_add(struct cdev *, dev_t, unsigned int)函數添加字符設備到系統中去。這樣以便于可以使用此設備驅動對其操作。在不使用設備時,調用cdev_del(struct cdev *)從系統中刪除字符設備。
2.2.3 分配和釋放設備號
在調用cdev_add()函數向系統注冊字符設備之前,應該先調用register_chrdev_region(dev_t from,unsigned count,const char *name)或者alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigend count, const char *name)函數向系統申請設備號。前者用于已知起始設備號的情況;后者用于設備號未知,向系統動態申請未被占用的設備號,如果函數調用成功,會把得到的設備號放入到參數dev中,alloc_chrdev_region()相比于register_chrdev_region()的優點在于它會自動避開設備號重復的沖突[1]。
相應的,在調用cdev_del()函數從系統注銷字符設備之后,unregister_chrdev_region(
dev_t from,unsigned count)函數用來釋放原先申請的設備號。
2.2.4 file_operations結構體
file_operations是字符設備驅動與內核虛擬文件系統的接口,應用程序通過linux系統調用,就會調用相應驅動程序的函數。這里給出file_operations結構體的主要成員函數。
struct file_operations
{
struct module *owner;
loff_t(*xxx_llseek)(struct file *, loff_t, int); ssize_t(*xxx_read)(struct file *, char __user, size_t, loff_t);
ssize_t(*xxx_write)(struct file *, const char __user, size_t, loff_t);
int(*xxx_open)(struct inode *, struct file *);
int(*xxx_release)(struct inode *, struct
file *);
......
}
xxx_llseek()函數用來修改一個文件的當前讀寫位置,并將新位置返回。xxx_read()函數用來從設備中讀取數據。它與用戶空間應用程序的read()函數和fread()函數在進行系統調用時對應。xxx_write()函數向設備發送數據,它與用戶空間應用程序的write()函數和fwrite()函數在進行系統調用時對應[6].
在Linux中,由于用戶空間和內核空間不能相互直接訪問數據,因此必須借助內核函數copy_from_user(void *to, const __user *from, unsigned long count)完成從用戶態的數據到內核態的復制,以及copy_to_user(void __user *to
,const void *from,unsigned long count)完成內核態數據到用戶態數據的復制。這樣內核態與用戶態就可以完成數據的讀寫了。
3.1 PCIE總線概述
PCIE總線是由Intel提出的最新的總線和接口標準,屬于高速點對點串行傳輸,所連的設備獨享通道帶寬。每個設備在傳輸數據的時候會各自建立相互獨立,互不干擾的的傳輸通道,數據傳輸的效率大大提高[5]。
3.2 PCIE設備驅動
3.2.1 PCIE設備驅動概述
PCIE設備驅動由兩部分組成:Linux內核的PCIE設備驅動和總線上所載設備本身的驅動。Linux內核的PCIE設備驅動由內核實現,它在安裝內核時就已經實現好了。因此本次實驗編寫的驅動就是總線上所載設備本身的驅動,也就是CH367總線的接口芯片的驅動程序。
PCIE設備有3種地址空間:配置空間、I/O空間、存儲器空間。配置空間是由Linux內核完成初始化代碼進行相應的操作,I/O空間和存儲器空間是由設備驅動程序訪問,CPU可以訪問所有的PCIE空間。PCIE至少包含256字節配置空間,前面64字節是標準化的必備的配置空間[4],后面的配置空間與具體的接口芯片本身相關。
3.2.2 PCIE設備驅動的注冊和注銷
PCIE設備驅動的注冊和注銷通過對pci_driver這個結構體實現的[3],注冊PCIE驅動使用函數pci_register_driver(struct pci_driver *driver),注銷設備使用pci_unregister_driver(struct pci_driver *driver);
PCIE設備驅動的注冊和注銷與字符設備驅動的注冊和注銷并無差異,知識函數調用不同而已,對于PCIE設備的初始化,我們使用pci_driver中的probe()函數,它負責探測硬件并保存配置信息。
3.2.3 PCIE設備驅動的數據結構
針對PCIE驅動,計算機主要完成對所有掛接在總線設備上的輪詢以及將設備添加到系統中,這樣便可以當做字符設備那樣操作PCIE設備,當驅動程序加載之后,硬件相應遍歷總線上的設備,如果識別到有相應設備掛接在總線上,那么接著就調用probe()函數進行地址映射、I/O或內存分配和映射,中斷號申請,并保存配置信息。其中pci_dev和pci_driver 這兩個結構體是對于完成PCIE驅動必不可的數據類型。
struct pci_dev
{
struct list_head global_list;
struct list_head bus_list;
struct pci_bus *bus; // PCIE總線的結構體
unsigned int devfn; // PCIE設備功能號
unsigned short vendor; // 設備產商ID
unsigned short device; // 設備ID
struct pci_driver *driver;
unsigned int irq; // 中斷號
......
}
pci_dev結構體詳細地描述了一個PCIE設備的硬件信息,包括掛接在總線上的信息和接口芯片本身的信息,如設備廠商ID、設備ID、中斷號等等。
struct pci_driver
{
struct list_head node;
char *name;
const struct pci_device_id *id_table;
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
void (*remove)(struct pci_dev *dev);
......
}
pci_driver結構體成員id_table包含驅動程序支持哪些設備的屬性列表。探測設備的函數probe()用于使能PCIE設備、I/O資源的申請、中斷申請和注冊字符設備[3]等相關操作以及卸載設備的函數remove()來注銷設備,釋放資源。
3.2.4 PCIE設備驅動的中斷
Linux內核的引導程序已經為每個PCIE設備分配了一個唯一的中斷號,它保存在配置空間的Interrupt_Line寄存器中,我們只需要使用讀取配置空間的內核函數就可以獲取中斷號[2]
pci_read_config_byte(dev, Interrupt_Line, &IRQ);
獲取中斷號后,然后再進行相應的中斷程序的處理。
4.1 開發環境搭建
操作系統為Ubuntu14.04,使用自帶的vim編輯器編寫驅動程序,Ubuntu14.04自帶了GCC編譯器,所以額外工作不需要做的很多,最后編寫Makefile文件。
將驅動程序和Makefile文件放在一個目錄中,然后在終端中打開文件所在目錄。編譯驅動程序使用命令:make。得到以.ko文件為后綴的驅動安裝文件。
4.2 驅動程序的安裝、卸載與調試
得到可執行文件后。使用下列命令安裝驅動、卸載與調試:
(1) insmod xxx.ko 加載驅動模塊
(2) cat /pro/devices 查看已經安裝設備驅動節點的設備號
(3) ls /dev/驅動名 查看驅動設備文件名
(4) rmmod xxx 卸載驅動模塊
驅動安裝完成之后,檢查有了設備號和設備節點名,如果安裝提示權限不夠,請在命令前加上sudo取得root權限,然后編寫應用程序進行相關調試,編寫好應用程序之后使用GCC編譯器編譯源代碼得到可執行的應用程序。使用下列命令命令:
(5) gcc xxx.c -o xxx 編譯應用程序
(6) . /xxx 運行應用程序
最后得到調試結果,實現了項目需求,實驗結果如圖3所示:
4.3 實驗數據分析
根據圖3所示,應用程序每次間隔一秒讀取時鐘卡上面的

圖3 實驗結果
時間信息,時間信息是GPS或者是北斗衛星通過串口給下位機,下位機解析時間信息之后傳送到雙口RAM,上位機機通過PCIE總線讀取雙口RAM時間信息,雙方設定好通訊協議。同時上位機還可以通過控制命令發送給下位機進行相應操作。數據第一列表示數據項的個數;第二列表示日期;第三列表示時間,后面的四位數代表200us時間刻度;第四列表示鎖定的衛星個數,且不為零則表示時間和日期信息均來至于GPS或者北斗時間信息,如果為零則表示時間和日期信息均來至于時鐘卡本身的實時時鐘;第五列表示B碼質量字節;第六列表示板卡為主卡或者從卡的工作方式:slave 或者master;最后一列就是對前面時間信息的顯示是來至于GPS或者北斗衛星的信息還是來至于實時時鐘的信息:RTC或者GPS。
本文以自主研發的PCIE同步時鐘卡為實際應用背景,闡明了Linux下字符設備驅動的開發以及相關的文件系統與數據結構,并結合實際項目做了PCIE驅動程序的設計。PCIE總線作為一種通用的總線接口標準,在目前的計算機系統中得到了廣泛的應用,不論是在Windows還是Linux操作系統中,其驅動程序的開發也備受關注,Linux作為開源的操作系統,對設備驅動支持具有更大的發展潛力,是今后的一個技術研究方向。
[1] 宋寶華. Linux設備驅動開發詳解[M]. 北京:機械工業出版社, 2016.
[2] 王學東,崔琪楣. Linux下的PCIE設備驅動設計與實現[EB/OL]. 北京:中國科技論文在線,2013.
[3] 王 軍,韓 力,杜博軍,等. 基于PCI-E總線的北斗導航授時卡Linux驅動設計[J].計算機測量與控制, 2016,24(4):115-117.
[4] 董春橋,李 凱. Linux系統PCI設備驅動程序開發[J]. 計算機測量與控制,2005,13(11):1289-1291.
[5] 周立國,梁淮寧,謝冬冬,等. 基于PCI Express總線的數據傳輸卡的設計與實現[J].電子測量技術,2007,30(11):28-31.
[6] 趙 明. 嵌入式PCI-E設備驅動程序的開發與應用[D]. 西安:西安電子科技大學,2014.
[7] 李 樺,高 飛,孫 磊. 嵌入式Linux設備驅動程序研究[J].微計算機信息,2010,26(5-2):68-70.
Device Driver Development of PCIE Synchronous Clock Card Based on Linux
Yang Bingjian,Wei Feng,Chen Yongzhi
(College of Automation,Huazhong University of Science and Technology, Wuhan 430074, China)
In order to provide the system accurate synchronization time and offer the high precision synchronous absolute time scale to most automation devices(such as fault wave record and data acquisition device),it can unite time benchmark for the parts in the system and also provide powerful basis with failure analysis and switch action sequence after system failure.Therefore, synchronous clock source is made based on the GPS and China's Beidou receiver double system which can receive the GPS(global positioning system) satellites and China's Beidou satellites positioning and timing information. The latest PCIE bus is designed to communicate and obtain time information with PC and synchronous clock source.This paper introduced driver programming development for the PCIE synchronous clock card based on Linux.It briefly analyses the synchronous clock card system framework and its working principle and reveals the Linux character device driver development process. Finally it achieves PCIE device driver development and implementation based on Linux operating system. The driver’s completeness and correctness are verified by experiment and can accurately read GPS receiver or Beidou system time information.
PCIE bus; device dirver; Linux; synchromous clock; kernel programming
2016-08-01;
2016-09-06。
楊兵見(1990-),男,湖北省荊州市人,碩士研究生,主要從事同步時鐘源、檢測技術、嵌入式軟件方向的研究。
1671-4598(2017)01-0098-03DOI:10.16526/j.cnki.11-4762/tp
TP
A