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