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);	
 }

 安装模块后执行应用程序输出:
   
      [root@/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

  这样太麻烦了,内核已经提供了相应的宏合成命令:
/* used to create numbers */
_IO(type,nr)            :定义一个没有参数的命令  
_IOR(type,nr,size)    :定义一个读方向的命令         
_IOW(type,nr,size)    :定义一个写方向的命令   
_IOWR(type,nr,size)     :定义一个数据双向的命令  
type:就是魔数
nr:功能码
size:参数大小,其实应该传递参数类型。(非指针)

也提供反向分解代码:
/* used to decode ioctl numbers.. */
#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 4)
                    count = 4;       
                copy_from_user(pdata, (const void __user *)arg,  count);             
                for(i=0;i 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;
 }   

 

你可能感兴趣的:(linux驱动,linux系统,linux驱动,ioctl)