歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

嵌入式Linux設(shè)備驅(qū)動(dòng)編寫原理

發(fā)布時(shí)間:2017-09-24 22:00  回復(fù):0  查看:2666   最后回復(fù):2017-09-24 22:00  

本文和大家分享的主要是嵌入式linux設(shè)備驅(qū)動(dòng)編寫原理相關(guān)內(nèi)容,一起來看看吧,希望對(duì)大家學(xué)習(xí)嵌入式有所幫助。

  驅(qū)動(dòng)簡(jiǎn)介

  Linux設(shè)備驅(qū)動(dòng)程序是內(nèi)核的一部分,它完成以下功能:

  l  對(duì)設(shè)備初始化和釋放

  l 把數(shù)據(jù)從內(nèi)核傳送到硬件和從硬件讀取數(shù)據(jù)

  l  讀取應(yīng)用程序傳送給設(shè)備文件的數(shù)據(jù)和回送應(yīng)用程序請(qǐng)求的數(shù)據(jù)

  l  檢測(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)用程序看來,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í)沒有緩存,只能順序訪問的設(shè)備,一般不能進(jìn)行任意長(zhǎng)度的I/O請(qǐng)求。典型的字符設(shè)備包括鼠標(biāo)、串行口、鍵盤等。字符設(shè)備接口支持面向字符的I/0操作,它不經(jīng)過系統(tǒng)的快速緩存,所以它們負(fù)責(zé)管理自己的緩沖區(qū)結(jié)構(gòu)。下面所描述的I2C接口屬于字符設(shè)備。

  塊設(shè)備的讀/寫都有緩存來支持,并且塊設(shè)備必須能夠隨機(jī)存取,字符設(shè)備則沒有這個(gè)要求。塊設(shè)備主要是針對(duì)磁盤等慢速設(shè)備設(shè)計(jì)的,以免損耗過多的CPU時(shí)間來等待。

  網(wǎng)絡(luò)設(shè)備在Linux里做專門的處理。Linux的網(wǎng)絡(luò)系統(tǒng)主要是基于BSD UnixSocket機(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)一般用來定義這個(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),這一過程在驅(qū)動(dòng)程序(模塊)的初始化過程中完成,調(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è)備的無關(guān)性,它把每個(gè)設(shè)備都抽象為文件系統(tǒng)的一個(gè)文件。Linux為每個(gè)設(shè)備在/dev目錄建立一個(gè)文件。例如,第一個(gè)軟驅(qū)在文件系統(tǒng)中的文件名為/dev/fd??梢允褂靡韵旅顏斫⒃O(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)作文件一樣來訪問,訪問文件和設(shè)備有以下函數(shù):seek、readwrite、pollio-control、memory mapopen、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)來與上面的文件訪問函數(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:用來修改一個(gè)文件當(dāng)前的讀寫位置,并將新位置做為返回值返回。出錯(cuò)時(shí)返回一個(gè)負(fù)的返回值。

  open:來為以后的操作完成作初始化準(zhǔn)備工作的。此外,open還增加設(shè)備計(jì)數(shù)(MOD_INC_USE_COUNT),以便防止文件在關(guān)閉前模塊被卸載出內(nèi)核。大部分驅(qū)動(dòng)程序中open完成如下工作:

  檢查設(shè)備相關(guān)錯(cuò)誤,如設(shè)備未就緒或類似的硬件問題。

  如果是首次打開,初始化設(shè)備。

  識(shí)別次設(shè)備號(hào),如有必要更新fop指針。

  分配和填寫要放在file->private_data里的數(shù)據(jù)結(jié)構(gòu)。增加使用計(jì)數(shù)。驅(qū)動(dòng)程序從來不知道被打開的設(shè)備名字,它僅僅知道設(shè)備號(hào)。

  release:使用計(jì)數(shù)減1,釋放在file->private_dataopen分配的內(nèi)存,在最后一次關(guān)閉操作時(shí)關(guān)閉設(shè)備。如果open沒有被調(diào)用,release也不會(huì)調(diào)用。它們?cè)谙到y(tǒng)調(diào)用間的關(guān)系保證了模塊使用計(jì)數(shù)永遠(yuǎn)是一致的(MOD_INC_USE_COUNTMOD_DEC_USE_COUNT)

  read、write:通過這兩個(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ù),完成到用戶空間和來自用戶空間的整個(gè)數(shù)據(jù)段的復(fù)制。

  利用函數(shù)copy_to_usercopy_from_user來完成用戶空間和內(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:最常用的通過設(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)用程序訪問被驅(qū)動(dòng)硬件的特殊功能:配置設(shè)備以及進(jìn)入或退出操作模式,這些控制操作通常無法通過read/write文件操作完成。

  下面以I2C驅(qū)動(dòng)的編寫為例進(jìn)行簡(jiǎn)要的說明:

  驅(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)的所有操作都通過ioctl來調(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)用程序通過si2c_ioctl來對(duì)I2C控制器進(jìn)行讀寫操作,由si2c_ioctl分別對(duì)讀寫函數(shù)進(jìn)行調(diào)用。進(jìn)行讀寫操作時(shí),I2C控制器使用中斷來與驅(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/OPB26PB27做為信號(hào),在初始化I2C時(shí)必需對(duì)PB口的狀態(tài)寄存器進(jìn)行配置,使這兩根信號(hào)線實(shí)現(xiàn)I2C接口功能。

  接下來,在CPMRAM中為I2C控制器的2個(gè)發(fā)送緩沖標(biāo)識(shí)符和2個(gè)接收緩沖標(biāo)識(shí)符申請(qǐng)空間。使用m8xx_cpm_dpalloc( )函數(shù)來申請(qǐng)地址空間,返回申請(qǐng)到的地址。將申請(qǐng)到的緩沖區(qū)地址指針分別賦給指針tbaserbase。

  在使用I2C控制器前,必須先配置好CPM ram中的I2C控制器的相關(guān)參數(shù),不用的參數(shù)置零。做為從設(shè)備,多址板使用I2C地址為0x34。在初始化過程中,還需禁止中斷,防止影響初始化工作。

  圖I2C驅(qū)動(dòng)流程及si2c_ioctl函數(shù)結(jié)構(gòu)

  應(yīng)用程序調(diào)用si2c_ioctl( )函數(shù)來控制驅(qū)動(dòng)。分別使用I2C_CMD_READI2C_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)?/span>I2C控制器做為從設(shè)備,在進(jìn)行讀操作之前,只需要初始化接收緩沖標(biāo)識(shí)符,并準(zhǔn)備好接收緩沖區(qū),這里的接收緩沖區(qū)由ioctl函數(shù)通過參數(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ù)通過interruptible_sleep_on (&iic_wait)進(jìn)入等待隊(duì)列,等待中斷發(fā)生。進(jìn)入中斷處理程序后,將控制器的中斷標(biāo)志位清零,并通過wake_up_interruptible (&iic_wait)喚醒讀寫函數(shù),返回等待的位置。

  驅(qū)動(dòng)調(diào)用結(jié)束后,系統(tǒng)會(huì)使用si2c_release( )函數(shù)來進(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)試過程

  設(shè)備驅(qū)動(dòng)程序僅僅處理硬件,如何使用硬件的問題屬于應(yīng)用程序。要測(cè)試驅(qū)動(dòng)程序的正確性,就應(yīng)該編寫相應(yīng)的應(yīng)用程序,對(duì)驅(qū)動(dòng)的各種功能進(jìn)行測(cè)試。

  在Linux系統(tǒng)中,應(yīng)用程序通過open、readwrite、ioctl等命令來調(diào)用驅(qū)動(dòng)程序。下面以一段調(diào)用驅(qū)動(dòng)寫操作的應(yīng)用程序?yàn)槔?,給出系統(tǒng)對(duì)應(yīng)用程序的響應(yīng)過程。

  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ù)首先通過文件標(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方法來對(duì)設(shè)備進(jìn)行寫的操作。其中1是在用戶空間進(jìn)行的,2-3是在內(nèi)核空間進(jìn)行的。通過系統(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ī)來模擬主設(shè)備的工作情況。在測(cè)試過程中,可以使用printf函數(shù)將驅(qū)動(dòng)中收到或發(fā)送的數(shù)據(jù)打印出來,方便觀察和調(diào)試。

 

 

來源:嵌入式Linux中文站

您還未登錄,請(qǐng)先登錄

熱門帖子

最新帖子

?