在前面的文章中,已經(jīng)描述了整一個字符設(shè)備模型,那現(xiàn)在,可以來使用相應(yīng)的模型進(jìn)行操作硬件設(shè)備了。那么,從點(diǎn)亮一個LED燈開始。 一、設(shè)備控制操作 1.理論基礎(chǔ) 大部分驅(qū)動程序除了需要提供讀寫設(shè)備的能力外,還需要具備控制設(shè)備的能力。比如設(shè)置UART波特率這樣的操作。 實際上在Linux系統(tǒng)用戶空間中,提供了ioctl系統(tǒng)調(diào)用函數(shù)來實現(xiàn)了控制設(shè)備的操作。其原型如下: int ioctl(int fd,unsigned long cmd,...) ·fd: 要控制的設(shè)備文件描述符 ·cmd: 發(fā)送給設(shè)備的控制命令 ·…: 第3個參數(shù)是可選的參數(shù),存在與否是依賴于控 制命令(第 2 個參數(shù) )。 當(dāng)應(yīng)用程序在用戶空間使用ioctl系統(tǒng)調(diào)用時,驅(qū)動程序?qū)⒂扇缦潞瘮?shù)來響應(yīng): (1)Linux 2.6.36版本之前的內(nèi)核 long (*ioctl) (struct inode* node ,struct file* filp, unsigned int cmd,unsigned long arg) (2)Linux 2.6.36版本之后的內(nèi)核 long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg); ·參數(shù)cmd: 通過應(yīng)用函數(shù)ioctl傳遞下來的命令 此函數(shù)方法由struct file_operations操作函數(shù)集實現(xiàn),如下圖: 在此筆者所使用的是3.0.8版本的Linux內(nèi)核。 2.控制方法實現(xiàn) (1)定義命令 命令從其實質(zhì)而言就是一個整數(shù), 但為了讓這個整數(shù)具備更好的可讀性,我們通常會把這個整數(shù)分為幾個段:類型(8位),序號,參數(shù)傳送方向,參數(shù)長度。 ·Type(類型/幻數(shù)): 表明這是屬于哪個設(shè)備的命令。 ·Number(序號):用來區(qū)分同一設(shè)備的不同命令。 ·Direction:參數(shù)傳送的方向,可能的值是 _IOC_NONE(沒有數(shù)據(jù)傳輸), _IOC_READ, _IOC_WRITE(向設(shè)備寫入?yún)?shù))。 ·Size:參數(shù)長度 Linux系統(tǒng)提供了下面的宏來幫助定義命令: ·_IO(type,nr):不帶參數(shù)的命令 ·_IOR(type,nr,datatype):從設(shè)備中讀參數(shù)的命令 ·_IOW(type,nr,datatype):向設(shè)備寫入?yún)?shù)的命令 示例: #define MEM_MAGIC ‘m’ //定義幻數(shù) #define MEM_SET _IOW(MEM_MAGIC, 0, int) (2)實現(xiàn)操作 在Linux內(nèi)核操作函數(shù)集中的unlocked_ioctl函數(shù)的實現(xiàn)通常是根據(jù)命令執(zhí)行的一個switch語句。但是,當(dāng)命令號不能匹配任何一個設(shè)備所支持的命令時,返回-EINVAL. 二、Linux內(nèi)核空間操作硬件設(shè)備 1.確定硬件接口 在操作一個硬件設(shè)備前,必須要了解的是其所使用的硬件接口和硬件原理。在此筆者所使用的是S5PV210平臺。 如上圖可知,LED1和LED2由CPU的GPC0_3和GPC0_4這兩個I/O口控制,并且GPIO口工作在輸出模式時,當(dāng)輸出高電平時,燈亮;輸出低電平時,燈滅。 如上圖為GPC0口的控制寄存器GPC0CON(地址為0xe0200060),分析可知,當(dāng)將[19:12]位配置為[00010001]時,GPC0_3和GPC0_4口工作在輸出模式下。 如上圖為GPC0口的數(shù)據(jù)寄存器GPC0DAT(0XE0200064),當(dāng)I/O口工作在輸出模式時,其每一位代表著每一個GPIO口的電平配置。所以GPC0DAT寄存器的位3代表著GPC0_3口,位4代表GPC0_4口。 在程序頭文件led_driver.h中定義寄存器硬件地址: 2.命令和設(shè)備結(jié)構(gòu)體定義 根據(jù)ioctl命令的定義方式,在程序頭文件led_driver.h中定義命令,如下圖: 如上圖中定義了一個led設(shè)備相關(guān)的結(jié)構(gòu)體struct led_device,成員struct class *led_class;代表led設(shè)備類,struct device *led_device;代表了的設(shè)備,unsigned int val;代表其可能需要的配置值。 對于LED的操作而言,實現(xiàn)可以單個點(diǎn)亮或熄滅LED,也可以全部點(diǎn)亮或熄滅LED。所以定義了__LED_SELECT枚舉、ON和OFF命令,然后通過__IO()宏進(jìn)行定義命令。 3.設(shè)備驅(qū)動模塊初始化 (1)定義struct led_device結(jié)構(gòu)體變量struct led_device *led_dev;和硬件操作相關(guān)變量gpc0con、gpc0dat。 并為指針led_device申請內(nèi)存空間。 (2)靜態(tài)申請主設(shè)備號 其中LED_MAJOR為靜態(tài)定義的主設(shè)備號,在程序頭文件led_driver.h中定義。led_fops為struct file_operations操作函數(shù)集。 #define LED_MAJOR 100 (3)自動創(chuàng)建設(shè)備節(jié)點(diǎn)文件 首先使用class_create函數(shù)創(chuàng)建一個設(shè)備類,然后再根據(jù)設(shè)備類由device_create函數(shù)創(chuàng)建一個名為led的設(shè)備節(jié)點(diǎn)文件,即為當(dāng)在Linux內(nèi)核中加載此驅(qū)動時,會自動在用戶空間/dev目錄下生成的設(shè)備節(jié)點(diǎn)文件led。 當(dāng)操作失敗時,處理方式如下: (4)將硬件設(shè)備操作寄存器地址映射為虛擬地址。 在Linux內(nèi)核中,是不允許直接操作設(shè)備的物理地址的,智能通過虛擬地址映射的方式進(jìn)行操作或者配置CPU私有外設(shè)的寄存器地址,從而達(dá)到操作相關(guān)寄存器的目的。 在Linux內(nèi)核中提供了ioremap宏函數(shù)來實現(xiàn)物理地址到虛擬地址的映射。 以上操作即為LED設(shè)備驅(qū)動的初始化函數(shù)led_init的實現(xiàn),源碼如下: 4.設(shè)備模塊卸載 設(shè)備模塊的卸載主要實現(xiàn)將設(shè)備號釋放,銷毀設(shè)備節(jié)點(diǎn)文件、銷毀設(shè)備類,釋放所申請的內(nèi)存空間。 5.操作函數(shù)集的實現(xiàn) 如上圖可知,一共實現(xiàn)了3個操作函數(shù)。分別對應(yīng)于用戶空間的open、close和ioctl系統(tǒng)調(diào)用的操作。其中led_open操作函數(shù)的實現(xiàn)主要進(jìn)行硬件寄存器的初始化配置。 如上圖即是配置GPC0CON寄存器,將GPC0_3和GPC0_4口配置為輸出模式,并初始化GPC0DAT寄存器,保證初始狀態(tài)為燈滅。 操作函數(shù)led_close的實現(xiàn)不實現(xiàn)任何操作。為空函數(shù)。 6.led_ioctl操作函數(shù)的實現(xiàn) 如上圖所示,led_ioctl函數(shù)通過接受從用戶空間傳遞而來的命令cmd,來決定LED設(shè)備的亮滅。以上這種方式是通過直接操作寄存器的方式來實現(xiàn)。實際上在Linux內(nèi)核中提供了相應(yīng)的讀寫寄存器的函數(shù)方法來實現(xiàn)操作,如下圖: 除了這種方法之外,要操作GOIO口,更可以使用gpio_get_value和gpio_set_value這樣的函數(shù)來進(jìn)行配置GPIO口,對于GPIO口的配置方式,還有更多的實現(xiàn)。 三、Linux用戶空間操作硬件設(shè)備 應(yīng)用程序的實現(xiàn)思路是,從外表為用戶程序提供命令來進(jìn)行操作LED燈,main函數(shù)從外表接收兩個命令: 命令的執(zhí)行狀態(tài)為:./app_led LED編號 狀態(tài) ·編號可取值1,2,3,分別表示LED1,LED2和全部LED。 ·狀態(tài)可取值0和1,分別表示燈亮和燈滅。 首先實現(xiàn)判斷外表命令的輸入情況,打開設(shè)備文件/dev/led,和將命令由字符轉(zhuǎn)化為整數(shù)。 然后就是點(diǎn)燈功能的實現(xiàn)了。 四、驗證現(xiàn)象 分別編譯好內(nèi)涵驅(qū)動模塊和應(yīng)用程序,分別得到led_driver.ko文件和app_led文件,然后將他們都拷貝到開發(fā)板根文件系統(tǒng)。 執(zhí)行命令:insmod led_driver.ko插入內(nèi)核驅(qū)動模塊。最后執(zhí)行應(yīng)用程序./app_led進(jìn)行操作LED。如下圖: 至此!整個點(diǎn)燈實現(xiàn)完成。 |