天天看點

linux裝置驅動标準ioctl接口

ioctl的驅動接口一般是作用在一些标準接口無法實作的功能。如和主要晶片連結的很多外設ic,主要對這些晶片的功能設定以及狀态的擷取等。是以 ioctl 接口可以看成是系統給我們進行功能擴充的的專用接口。

系統調用接口原型:

int ioctl(int d, int request, ...);
           

這個函數是一個可變參數函數,最少需要2個參數

參數:

d: 是檔案描述符号

   request: 通常是 cmd 。dongjieko

     ...:可變參數,可以有,也可以沒有,根據 request 情況而定。     

     示例:

     一個指令: 所有燈開   --     隻需要一個cmd ,不需要其他參數

     一個指令:指定燈開   --     需要一個cmd ,還需要知道操作哪個燈的, 這種情況下就需要第3個參數。

   傳回值:

   成功:通常是傳回 0, 如果是非标準的cmd,傳回是使用者自定義的正數。  如,可以使用這個接口實作llseek功能。

   失敗:傳回 的是-1;

驅動程式 ioctl 接口原型:

   3.0以上的核心,(2。6核心是 ioctl)

  long (*unlocked_ioctl) (struct file *, unsigned int  cmd, unsigned long  arg);

  file: 檔案描述通過VFS轉換而來

  cmd:就是應用程式傳遞下來的  request 參數

  arg: 對應于應用程式傳遞下第三個參數(變參);

如以下的驅動執行個體:

//ioctl(fd,cmd,arg)
long chrdev_unlocked_ioctl (struct file *pfile, 
                                                                 unsigned int cmd, 
                                                                  unsigned long arg)
 {

           switch ( cmd )
           {
               case 0 :
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                   break;
               case 1 :
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                   break;
               case 2 :
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                   break;
               case 3 :
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                   break;
               case 4 :               
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);                   
                   break;
               default:            
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                   return - EINVAL;
                   
           }

          return arg;
 }
           

比如:應用程式的代碼:

for(i=0;i<6;i++)
 {
        int ret ;
    	ret = ioctl(fd,i,10+i); //args 從10開始
      	sleep(1);  	
    	printf("\nret = %d\n",ret);	
 }
           

 安裝子產品後執行應用程式輸出:

      [[email protected]/home]# ./app 

    [ 8698.345000] line:102,cmd:0,arg:10

    ret = 10

    line:105,cmd:1,arg:11

    ret = 11

                           //此處應該是12,但是沒有執行到。

    ret = -1

    line:111,cmd:3,arg:13

    ret = 13

    line:114,cmd:4,arg:14

    ret = 14

    line:117,cmd:5,arg:15

    ret = -1

    結果:

    1。驗證以上所說的參數對應關系 

    2。驗證以上所的傳回值

    3。當cmd的值為2時,驅動程式沒有執行!

重點分析一下第3個現象:

      ioctl接口比較特殊,不像其他接口通過fd直接找到驅動,

      ioclt 接口的cmd值在核心中有規範。不是随便可以使用一個數字就可以了。有特定的編碼規則。

      當cmd值和系統一些預定義指令相同時,代碼所執行不會是我們的驅動代碼中的ioctl,而是被攔截了,去執行相同的預定義指令去了。

      上面的2這個值就屬于這種現象。。。 從2。6核心到現在,cmd為2的值不能使用的。

      要想安全使用ioctl接口,必須使用核心規定的定義方法來定義cmd值。

核心文檔 Ioctl-decoding.txt (documentation\ioctl)     有說明:

bit 含義
31-30

00-no parameters: uses _IO macro系統調用  ioctl ,當cmd值的31:30為00時候,應該 ioctl(fd,cmd),不能有3個參數,否則[可能]和其他指令沖突。

10 - read: _IOR代表想通過ioctl從驅動中讀取資料回來,存放在 ioctl(fd,cmd,arg)中的argr所代表的記憶體空間中(arg應該是一個可寫記憶體位址)。

01 - write: _IOW代表想通過ioctl從向驅動中寫入資料,資料存放在 ioctl(fd,cmd,arg)中的arg所指向記憶體空間中(arg應該是一個可讀記憶體位址)。

11 - read/write: _IOWR代表想通過ioctl從向驅動中寫入資料,資料存放在 ioctl(fd,cmd,arg)中的arg所指向記憶體空間中(arg應該是一個可讀記憶體位址)。

29-16

就是參數大小  14位,最多隻能傳遞16K資料

當 cmd   31-30    不為0時候表示 調用方法 ioctl(fd,cmd,arg)  ,表示有資料傳遞。

15-8

驅動 魔數/幻數,範圍00~FF用來辨別一個驅動,原則上講一個驅動有惟一值,隻要這個值不同,就不會和其他指令沖突。

核心中已經占用大部分的魔數。原則可以由使用者定義。核心文檔 Ioctl-number.txt (documentation\ioctl)告訴數字已經被使用了。

7-0

function   指令功能  範圍:0~255

其實這個才是真正的指令碼。像前面的0,1,2,3

示例:

  定義一個關燈指令:

  #define LED_X_OFF        1<<30 |  1<<16 |  'A' <<8 | 0

   定義一個開燈指令:

  #define LED_X_ON        1<<30 |  1<<16 |  'A' <<8  | 1

  定義一個全部燈關指令:

  #define LED_ALL_OFF     0<<30 |  0<<16 |  'A' <<8 | 2

   定義一個全部開燈指令:

  #define LED_ALL_ON     0<<30 |  0<<16 |  'A' << 8  | 3       

  定義一個讀指定燈狀态指令:

  #define LED_X_STATUS  2<<30 |  1<<16 |  'A' <<8 | 4

  這樣太麻煩了,核心已經提供了相應的宏合成指令:

_IO(type,nr)            :定義一個沒有參數的指令  

_IOR(type,nr,size)    :定義一個讀方向的指令         

_IOW(type,nr,size)    :定義一個寫方向的指令   

_IOWR(type,nr,size)     :定義一個資料雙向的指令  

type:就是魔數

nr:功能碼

size:參數大小,其實應該傳遞參數類型。(非指針)

也提供反向分解代碼:

#define _IOC_DIR(nr)       取出方向值

#define _IOC_TYPE(nr)   取魔數

#define _IOC_NR(nr)        取出功能碼

#define _IOC_SIZE(nr)        取出大小

 所在上面的定義可以修改:

   示例:

  定義一個關燈指令:

     #define LED_MAGI          'A'

   #define LED_MAX          4

  #define LED_X_OFF       _IOW(LED_MAGI,0,char)     // 1<<30 |  1<<16 |  'A' <<8 | 0

   定義一個開燈指令:

  #define LED_X_ON        _IOW(LED_MAGI,1,char)  // 1<<30 |  1<<16 |  'A' <<8  | 1

  定義一個全部燈關指令:

  #define LED_ALL_OFF     _IO(LED_MAGI,2)    //0<<30 |  0<<16 |  'A' <<8 | 2

   定義一個全部開燈指令:

  #define LED_ALL_ON    _IO(LED_MAGI,3)    // 0<<30 |  0<<16 |  'A' << 8  | 3       

  定義一個讀指定燈狀态指令:

  #define LEDS_STATUS _IOR(LED_MAGI,4,int)    // 2<<30 |  1<<16 |  'A' <<8 | 4  

   //每位元組存放一個燈狀态,    驅動中有4個燈。

  以上内容單獨定義在一個.h檔案中,然後把drv,app放在在相同目錄下。

   drv,app都包含相同的頭檔案 就可以了。

應用程式使用:

  int main()
  {
      
      ....
      char led_nr;
      int state;
      //關指定燈
   led_nr =2;      
      ioctl(fd,LED_X_OFF,&lednr);
      
       //關所有燈
      ioctl(fd,LED_ALL_OFF);     
      
       //讀燈狀态
      ioctl(fd,LEDS_STATUS,&state);
      
  }
           

驅動代碼:

      //ioctl(fd,cmd,arg)
long chrdev_unlocked_ioctl (struct file *pfile, 
                                                                 unsigned int cmd, 
                                                                  unsigned long arg)
 {  
           char  *pdata;
           int count =  _IOC_SIZE(cmd) ;  //取得複制數量
           
           
           //出于效率還要做一個判斷
           if(_IOC_TYPE(cmd) != LED_MAGI)
                   return -1;
           
           
           if(_IOC_NR(cmd) >  LED_MAX)
                   return -1;
                
                      
           switch ( cmd )
           {
               case LED_X_OFF :    
                printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                pdata = kmalloc( _IOC_SIZE(cmd));  //配置設定緩沖區
                if(_IOC_SIZE(cmd) > 4)
                    count = 4;       
                copy_from_user(pdata, (const void __user *)arg, count); 
                
                for(i=0;i<count;i++)
                {
                    rGPM4DAT |=  (1 << (0 + pdata[i]));   /*滅 */                          
                }    
                kfree(pdata);
                      
               case LED_X_ON :
                printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                if(_IOC_SIZE(cmd) > 4)
                    count = 4;       
                copy_from_user(pdata, (const void __user *)arg,  count);             
                for(i=0;i<count;i++)
                {
                    rGPM4DAT &= ~ (1 << (0 + pdata[i]));   /* 亮 */                          
                } 
                break;
                   
               case LED_ALL_OFF :
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                   //for 可以
                    rGPM4DAT |=   (1 << (0 + 0);   /* 滅 */                  
                    rGPM4DAT |=   (1 << (0 + 1);   /* 滅 */     
                    rGPM4DAT |=   (1 << (0 + 2);   /* 滅 */     
                   rGPM4DAT |=   (1 <<  (0 + 3);   /* 滅 */                                 
                   break;
                   
               case LED_ALL_ON:
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                   //for 可以
                    rGPM4DAT &= ~  (1 << (0 + 0);   /* 滅 */                  
                    rGPM4DAT &= ~  (1 << (0 + 1);   /* 滅 */     
                    rGPM4DAT &= ~  (1 << (0 + 2);   /* 滅 */     
                   rGPM4DAT  &=  ~ (1 <<  (0 + 3);   /* 滅 */                            
                   break;
                   
               case LEDS_STATUS :               
                printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);  
                if(_IOC_SIZE(cmd) > 4)
                    count = 4;  
                               
                pdata = kmalloc( count);  //配置設定緩沖區
                for(i=0;i< count;i++)
                {
                    pdata[i] = !( rGPM4DAT & 1<<(0 + i) );           
                }
                copy_to_user( arg, pdata, _IOC_SIZE(cmd)); 
                kfree(pdata);                   
                break;
                   
               default:            
                   printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
                   return - EINVAL;             
           }              
          return 0;
 }   

           

繼續閱讀