驅動簡介
Linux設備驅動程序是內核的一部分,它完成以下功能:
?? ? ? ? 對設備初始化和釋放
?? ? ? ? 把數據從內核傳送到硬件和從硬件讀取數據
?? ? ? ? 讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據
?? ? ? ? 檢測和處理設備出現的錯誤。
系統調用是操作系統內核和應用程序之間的接口,設備驅動程序是操作系統內核和機器硬件之間的接口。Linux設備驅動程序為應用程序屏蔽了硬件細節,在應用程序看來,Linux硬件設備只是一個設備文件,應用程序可以像操作普通文件一樣對硬件設備進行操作。每個設備驅動程序都具有以下幾個特性:
1.? ? ? ? 具有一整套的和硬件設備通訊的例程,并且提供給操作系統一套標準的軟件接口;
2.? ? ? ? 具有一個可以被操作系統動態地調用和移除的自包含組件;
3.? ? ? ? 可以控制和管理用戶程序和物理設備之間的數據流。
驅動類型
Linux設備分為三種:字符設備、塊設備和網絡設備。
字符設備是指存取時沒有緩存,只能順序訪問的設備,一般不能進行任意長度的I/O請求。典型的字符設備包括鼠標、串行口、鍵盤等。字符設備接口支持面向字符的I/0操作,它不經過系統的快速緩存,所以它們負責管理自己的緩沖區結構。下面所描述的I2C接口屬于字符設備。
塊設備的讀/寫都有緩存來支持,并且塊設備必須能夠隨機存取,字符設備則沒有這個要求。塊設備主要是針對磁盤等慢速設備設計的,以免損耗過多的CPU時間來等待。
網絡設備在Linux里做專門的處理。Linux的網絡系統主要是基于BSD Unix的Socket機制。在系統和驅動程序之間定義有專門的數據結構進行數據的傳遞。系統里支持對發送資料和接受資料的緩存,提供流量控制機制,提供對多協議的支持。
主次設備號
Linux給每個設備都分配一個主設備和次設備號。主設備號一般用來定義這個設備的類型。例如軟驅的主設備號是2,并行端口的主設備號是6。次設備號是一個8位的數字,它指定一個特定的設備,例如一臺電腦可以有2個軟驅,它們都有主設備號2,但是第一個軟驅的次設備號為1,而第二個軟驅的次設備號為2。
在任何程序使用設備驅動程序之前,設備驅動程序應該向系統進行登記,以便系統在適當的時候調用。向系統增加一個驅動程序即給它一個主設備號,這一過程在驅動程序(模塊)的初始化過程中完成,調用如下函數:
int register chrdev(major,*name ,*fops)
參數major是所請求的主設備號,name是設備的名字,它們將在/proc/devices文件中出現,fops是一個指向跳轉表的指針,利用這個跳轉表完成對設備函數的調用。
從系統中卸載一個模塊時,應該釋放主設備號。這一操作可以在cleanup_module中調用如下函數完成:
int unregister chrdev (major,*name)
參數是要釋放的主設備號和相應的設備名。內核對與這個名字和設備號對應的名字進行比較,如果不同或者主設備號超出了允許的范圍或是并未分配給這個設備,內核返回ENINVAL。
文件操作
Linux具有設備的無關性,它把每個設備都抽象為文件系統的一個文件。Linux為每個設備在/dev目錄建立一個文件。例如,第一個軟驅在文件系統中的文件名為/dev/fd??梢允褂靡韵旅顏斫⒃O備文件:
mknod??/dev/device_name??device_type??major_number??ninor_number
其中device_name是此設備的文件,device_type是設備的類型(c表示字符設備,b表示塊設備)。
Linux系統把設備當作文件一樣來訪問,訪問文件和設備有以下函數:seek、read、write、poll、io-control、memory map、open、flush、release、check、lock等。編寫設備驅動程序的主要工作就是編寫子函數,并填充file-operations的各個域。并不一定要實現所有的函數,只需要實現設備必須的函數就可以了。
設備驅動使用類型為struct file_operations的一個數據結構來與上面的文件訪問函數對應。一般的字符設備驅動程序適用的file-operations結構如下:
struct file_operation dev_fiops{
dev_lseek,
dev_read,
dev_write,
dev_ioctl,
dev_open,
dev_release,
};
lseek:用來修改一個文件當前的讀寫位置,并將新位置做為返回值返回。出錯時返回一個負的返回值。
open:來為以后的操作完成作初始化準備工作的。此外,open還增加設備計數(MOD_INC_USE_COUNT),以便防止文件在關閉前模塊被卸載出內核。大部分驅動程序中open完成如下工作:
檢查設備相關錯誤,如設備未就緒或類似的硬件問題。
如果是首次打開,初始化設備。
識別次設備號,如有必要更新fop指針。
分配和填寫要放在file->private_data里的數據結構。增加使用計數。驅動程序從來不知道被打開的設備名字,它僅僅知道設備號。
release:使用計數減1,釋放在file->private_data中open分配的內存,在最后一次關閉操作時關閉設備。如果open沒有被調用,release也不會調用。它們在系統調用間的關系保證了模塊使用計數永遠是一致的(MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT)。
read、write:通過這兩個函數可以像使用文件那樣向設備傳送數據,ssize(*write)(*filp, *buff, count, *offp)和 ssize(*read)(*filp, *buff, count, *offp)其中filp是文件指針,buff是指向用戶的緩沖區,count是傳入數據的長度,offp是用戶在文件中的位置。當成功時返回值就是寫入或讀取的數據長度。用write函數向打開的文件寫數據,用read函數從打開的文件中讀數據,完成到用戶空間和來自用戶空間的整個數據段的復制。
利用函數copy_to_user和copy_from_user來完成用戶空間和內核空間數據的傳輸。
Unsigned long copy_to_user(*to, *from, count)和unsigned long copy_from_user(*to, *from, count)其中to是指向數據目的緩沖區,from是指向數據源緩沖區,count是數據的長度。當成功時,返回值就是寫入或讀出長度,失敗返回-EFAULT。
ioctl:最常用的通過設備驅動完成控制動作的方法。ioctl的調用為驅動程序執行“命令”提供了一個與設備相關的入口點。與read和其他方法不同,ioctl是與設備相關的,允許應用程序訪問被驅動硬件的特殊功能:配置設備以及進入或退出操作模式,這些控制操作通常無法通過read/write文件操作完成。
下面以I2C驅動的編寫為例進行簡要的說明:
驅動結構
在***系統中,I2C接口主要執行讀寫操作,完成與**部分的數據收發工作。
根據I2C接口所需要的功能,驅動程序的file_operations結構如下:
static struct file_operations si2c_ops={
? ? ? ? open:? ? ? ? ? ? ? ? si2c_open,
? ? ? ? release:? ? ? ? si2c_release,
? ? ? ? ioctl:? ? ? ? ? ? ? ? si2c_ioctl,
};
驅動中主要函數如下:
int si2c_init (void):初始化I2C控制器,在系統中注冊驅動,并初始化通信處理器CPM。
static int si2c_open (struct inode *inode, struct file *file):打開設備的第一個操作,標示設備打開,并進行加一計數。
static int si2c_release (struct inode *inode, struct file *file):當驅動程序關閉時,系統調用該函數。與open函數對應。
static int si2c_ioctl (struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg):應用程序對驅動的所有操作都通過ioctl來調用。
static void si2c_interrupt (void *dev_id):負責處理收發數據和出錯時產生的中斷。
static void si2c_reset_params (volatile iic_t *iip):重新設置I2C CPM中控制通道的參數。
static void si2c_force_close (void):使用CPM_CR_CLOSE_RXBD命令關閉I2C通信。
extern ssize_t si2c_read (si2c_request_t *req):讀I2C總線上的數據。
extern ssize_t si2c_write(si2c_request_t *req):寫I2C總線上的數據。
應用程序通過si2c_ioctl來對I2C控制器進行讀寫操作,由si2c_ioctl分別對讀寫函數進行調用。進行讀寫操作時,I2C控制器使用中斷來與驅動進行數據交互。
驅動實現
流程圖如圖
安裝驅動程序時,系統會調用初始化函數si2c_init( )進行初始化工作。在初始化程序中,對I2C設備進行注冊,使用register_chrdev( ) 返回主設備號。
I2C接口使通用I/O的PB26、PB27做為信號,在初始化I2C時必需對PB口的狀態寄存器進行配置,使這兩根信號線實現I2C接口功能。
接下來,在CPM的RAM中為I2C控制器的2個發送緩沖標識符和2個接收緩沖標識符申請空間。使用m8xx_cpm_dpalloc( )函數來申請地址空間,返回申請到的地址。將申請到的緩沖區地址指針分別賦給指針tbase和rbase。
在使用I2C控制器前,必須先配置好CPM ram中的I2C控制器的相關參數,不用的參數置零。做為從設備,多址板使用I2C地址為0x34。在初始化過程中,還需禁止中斷,防止影響初始化工作。?
圖I2C驅動流程及si2c_ioctl函數結構
??? 應用程序調用si2c_ioctl( )函數來控制驅動。分別使用I2C_CMD_READ和I2C_CMD_WRITE執行讀寫命令。在ioctl中這兩個命令會分別調用si2c_read( )和si2c_write( )函數。驅動程序與用戶緩沖區交互使用函數copy_from_user( )和copy_to_user( ),前者從用戶緩沖區讀數據,后者將數據復制到用戶數據緩沖區。
讀數據:因為I2C控制器做為從設備,在進行讀操作之前,只需要初始化接收緩沖標識符,并準備好接收緩沖區,這里的接收緩沖區由ioctl函數通過參數傳入。在讀函數打開中斷,啟動I2C控制器進行讀操作之后,等待中斷產生,待中斷返回后,檢查狀態寄存器是否出錯,進行相應操作后返回狀態值。
寫數據:在啟動一次寫操作前,驅動程序需預先配置好發送描述符,將描述符指向的ioctl傳入的發送緩沖區。些函數打開中斷,啟動I2C控制器后,等待中斷發生,待中斷返回后,檢查狀態寄存器,并返回狀態值。
I2C控制器使用中斷與驅動通信,中斷由Linux系統管理,在Linux系統里,對中斷的處理是屬于系統核心的部分,讀寫函數與中斷程序交互的操作由信號量實現。讀寫函數通過interruptible_sleep_on (&iic_wait)進入等待隊列,等待中斷發生。進入中斷處理程序后,將控制器的中斷標志位清零,并通過wake_up_interruptible (&iic_wait)喚醒讀寫函數,返回等待的位置。
驅動調用結束后,系統會使用si2c_release( )函數來進行減一操作并關閉I2C控制器。當使用rmmod name命令卸載驅動程序時,系統會調用cleanup_module( ),釋放申請的存儲空間,注銷驅動設備。
驅動程序編寫完畢,編寫Makefile文件,具體格式如下:
KERNELDIR = /home/adhoc/linux-2.4.4
LD = powerpc-linux-gcc
CFLAGS = -D__KERNEL__ -I/home/adhoc/linux-2.4.4/include -Wall?
-Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing?
-D__powerpc__ -fsigned-char -msoft-float -pipe -fno-builtin -ffixed-r2
-Wno-uninitialized -mmultiple -mstring -mcpu=860??-DMODULE?
-DMODVERSIONS -include?
/home/adhoc/linux-2.4.4/include/linux/modversions.h
all: clean i2c.o?
i2c.o: i2c.c?
? ? ? ? $(LD) $(CFLAGS)??-c $^ -o $@
clean:
? ? ? ? rm -f *.o *. core
Makefile完成之后,在驅動文件所在目錄下運行make命令,編譯生成可執行文件i2c.o, 使用mknod /dev/i2c c 42 0在系統/dev目錄下建立設備文件節點,驅動主設備號42,次設備號0,然后用insmod命令將驅動安裝在系統中,供應用程序調用。
調試過程
設備驅動程序僅僅處理硬件,如何使用硬件的問題屬于應用程序。要測試驅動程序的正確性,就應該編寫相應的應用程序,對驅動的各種功能進行測試。
在Linux系統中,應用程序通過open、read、write、ioctl等命令來調用驅動程序。下面以一段調用驅動寫操作的應用程序為例,給出系統對應用程序的響應過程。
int main(){
? ? ? ? int file_desc;
? ? ? ? si2c_request_t *i2c_data,*temp;
? ? ? ? int len,i;
? ? ? ? i2c_data=(si2c_request_t *)malloc(sizeof(si2c_request_t));
? ? ? ? temp=(si2c_request_t *)malloc(sizeof(si2c_request_t));
? ? ? ? i2c_data->dlen=1;
? ? ? ? for(i=0;idlen;i++)? ? ? ? {
? ? ? ? i2c_data->data=i;
? ? ? ? }
? ? ? ? printf("start test .../n");
? ? ? ? file_desc = open("/dev/i2c",O_RDWR);
? ? ? ? if(file_desc<0){
? ? ? ? ? ? ? ? printf("Can't open device file:%s/n",DEVICE_NAME);
? ? ? ? ? ? ? ? exit(-1);
? ? ? ? }
? ? ? ??
? ?? ?? ?? ?? ? ioctl(file_desc,I2C_CMD_WRITE,i2c_data);
? ? ? ? len=i2c_data->dlen;
? ? ? ? printf("len=%d./n",len);
? ? ? ? close(file_desc);
? ? ? ? free(i2c_data);
? ? ? ? free(temp);
? ? ? ? return 0;
}
1.? ? ? ? 用戶程序使用open打開設備節點文件,這時操作系統內核知道該驅動程序工作了,就調用fops方法中的open函數進行相應的工作。open方法一般返回的是文件標示符,實際上并不是直接對它進行操作的,而是有操作系統的系統調用在背后工作。
2.? ? ? ? 當用戶使用write函數操作設備文件時,操作系統調用syswrite函數,該函數首先通過文件標識符得到設備節點文件對應的inode指針和flip指針。inode指針中有設備號信息,能夠告訴操作系統應該使用哪一個設備驅動程序,flip指針中有fops信息,可以告訴操作系統相應的fops方法函數在哪里可以找到。
3.? ? ? ? 然后這時syswrite才會調用驅動程序中的write方法來對設備進行寫的操作。其中1是在用戶空間進行的,2-3是在內核空間進行的。通過系統調用sys_write將用戶的write函數和操作系統的write函數聯系在了一起.
在多址硬件系統中,I2C接口作為從屬設備,而從屬設備必須有主設備的驅動才能工作,因此要測試驅動程序,還必須模擬出一個主設備。我們用單片機來模擬主設備的工作情況。在測試過程中,可以使用printf函數將驅動中收到或發送的數據打印出來,方便觀察和調試。
?
?
評論
查看更多