linux设备驱动标准ioctl接口

it2024-11-12  16

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

 

最新回复(0)