一次意外的ioctl调用错误—论不同版本内核的ioctl函数实现

前言

         人要经常学会反思总结,这样才能不断的进步。同样,一个合格优秀的软件工程师也需要经常的总结反思自己工作中遇到的问题,这样才能使自己的能力不断的提升。我从来不给自己定位为程序猿,多年后,请叫我软件工程师乃至软件架构师,哈哈,又装逼了我!纵使我现在还年轻,能力阅历还未达到大师级别,但是人要有梦想,万一实现了呢?可是相比较梦想,我觉得有目标比较实际。卧槽,每次写博客都是这样,先是装逼似的跑题一下,然后再进入正题,哈哈,读者莫见怪,只是为了让文章更加生动有趣。OK,今天我要说的是:“一次意外的ioctl调用错误—论不同版本内核的ioctl函数实现!!”
     这个问题来源于我公司的一个项目:“BCMXXXX平台无线modem驱动移植!”当时的问题是:模块上电时序都正确、可以正常工作,可是用POS机上的拨号测试程序去测试无线modem是否拨号成功的时候,出现了错误!具体错误在应用层测试程序通过ioctl函数调用驱动层的ioctl时候,出现ioctl调用无效的错误,命令无法被识别。这到底是什么问题呢?后来查了半天才知道原因:原来是由于不同版本的内核的ioctl函数实现不一样导致的!!

1 问题描述

        驱动移植之后,无线模块可以正常开启了,但是使用POS自带的测试程序测试的时候却发现测试异常。这个问题花了我两天时间才解决,一开始请教别人的时候,他们说可能是应用层NDK的问题,于是我就自己写了一个无线模块的测试程序去判断到底是驱动问题还是应用层的NDK问题。

    我的测试程序如下图所示,通过应用层open函数打开无线驱动设备文件,然后用ioctl获取无线modem模块的类型。应用层ioctl传值给驱动层的ioctl函数,驱动层调用相应的命令获取无线模块的类型。

一次意外的ioctl调用错误—论不同版本内核的ioctl函数实现_第1张图片

        可是结果却是应用层的ioctl传的命令值到驱动层就变了,导致驱动层ioctl函数无法识别应用层传来的命令值是多少?这到底是什么原因呢?后来经过调试才发现,不同平台的CPU使用的内核版本不一样,导致ioctl接口函数实现不一样导致的。

2 不同版本内核的ioctl函数实现

Linux-2.6.36之前的内核:
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
(1)inode和file:ioctl的操作有可能是要修改文件的属性,或者访问硬件。要修改
文件属性的话,就要用到这两个结构体了,所以这里传来了它们的指针;
(2)cmd:从用户空间传下来的命令参数;
(3)arg:可选参数,主要用于定义应用层和驱动层进行命令调用时候是否涉及数据读写;
Linux-2.3.36之后的内核:
int (*ioctl)(struct file *filp,unsigned int cmd,unsigned long arg)
(1)file:ioctl的操作有可能是要修改文件的属性,或者访问硬件。要修改
文件属性的话,就要用到这两个结构体了,所以这里传来了它们的指针;
(2)cmd:从用户空间传下来的命令参数;
(3)arg:可选参数,主要用于定义应用层和驱动层进行命令调用时候是否涉及数据读写;
   从linux-2.6.36之后,已经由unlocked_ioctl替代原来的ioctl。其中驱动的变化就是函数参数去掉inode参数。这也是我前面提及到的我在项目开发过程中遇到的问题所在。那时候项目开发时,旧平台的内核是Linux-2.6.32的,新平台的内核是Linux-3.6.5的,所以要修改ioctl函数,去掉struct inode *inode这个参数就可以了。在驱动开发中,如果大家遇到像我这样ioctl调用失效的问题,可以查看一下内核版本,然后看看是否是ioctl函数实现不一样导致的!

3 再论Linux内核ioctl函数

3.1 什么是ioctl?

   ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:
   int ioctl(int fd, ind cmd, …);
   其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

3.2 ioctl实现原理

    在驱动当中实现的ioctl函数中,其实就是一个switch case的结构,每一个case对应一个命令码,对应一些命令操作。如何实现这些操作是软件工程师根据程序开发的需求来定制,因为每一个设备都是特定的,关键在于如何组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序的路径。
    在Linux内核中是这样定义一个命令码的:
    | 设备类型 | 序列号 | 方向 | 数据尺寸 |
    |----------|--------|------|----------|
    |   8bit   |  8bit  | 2bit | 2~14bit  |
    |----------|--------|------|----------|
    其实,在Linux内核中,命令就是一个整数形式的命令码。可是这样复杂的命令码看起来不够直观,开发人员很难记住,为此,Linux内核专门为这些命令码提供了一些宏,这些宏可以根据便于理解的字符串生成命令码,或者是从,命令码中得到一些用户可以理解的字符串用来标明相应设备的设备类型、设备序列号、数据传输方向、数据传输尺寸。

3.2.1 Linux内核ioctl命令宏定义详解

    在设备驱动开发中,应用层和驱动层进行命令交互和数据传输过程中无非这四种情况:
    1.无任何参数的命令控制;
    2.应用层从驱动中读取数据;
    3.应用层写数据到驱动层;
    4.应用层和驱动层进行双向的命令交互;
    在Linux内核中定义了以下宏用来定义命令码:
//nr为序号,datatype为数据类型,如int
_IO(type, nr ) //没有参数的命令
_IOR(type, nr, datatype) //从驱动中读数据
_IOW(type, nr, datatype) //写数据到驱动
_IOWR(type,nr, datatype) //双向传送
    ioctl命令定义实例展示:(液晶驱动ioctl代码截取)
#define DISP_IOC_MAGIC 'disp' //定义类型
#define DISP_IOCSET _IOW(DISP_IOC_MAGIC,0,int)
#define DISP_IOCGQSET _IOR(DISP_IOC_MAGIC, 1, int)

3.2.2 ioctl函数命令实现

      实现ioctl函数包含以下三个技术环节:
(1) 返回值:
    ioctl函数根据应用层传入的相应命令到驱动层的ioctl去匹配相应的命令,命令的执行在一个switch语句中,如果命令不匹配,通常返回一个-ENVAL值;
(2) 参数使用:
    用户使用 int ioctl(int fd,unsigned long cmd,....)时候传递参数;
    驱动层调用 int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)中的arg参数进行与应用层的参数传递,如果arg是一个整数,可以直接使用,如果是一个指针,则要判断指针的地址的合法性,因此使用之前需要进行正确的检查。不需要检查的是:
copy_from_user
copy_to_user
get_user
put_user
    需要检查的是:
_get_user
__put_user
    检测函数access_ok():
static inline int access_ok(int type, const void *addr, unsigned long size)
/*
type :是VERIFY_READ 或者VERIFY_WRITE用来表明是读用户内存还是写用户内存;
addr:是要操作的用户内存地址;
size:是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数就等于sizeof(int);

返回值:Access_ok返回一个布尔值:1,是成功(存取没问题);0,是失败,ioctl返回-EFAULT;

*/

3.3 驱动ioctl函数代码实现框架

static int xxx_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
     switch (cmd) 
     {
	case XXX_IOCTL_CMD_1:
        {
	    //to do
        }
        break; 
        case XXX_IOCTL_CMD_2:
        {
	    //to do
        }
        break;  
        default:
           break;
     }
}    
     OK,关于ioctl函数的实现,就总结到这里吧,欢迎大家提意见和互相学习!!!









你可能感兴趣的:(Linux驱动开发,Linux内核)