Chinaunix首页 | 论坛 | 博客
  • 博客访问: 121496
  • 博文数量: 15
  • 博客积分: 1581
  • 博客等级: 上尉
  • 技术积分: 411
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-29 09:48
文章分类

全部博文(15)

文章存档

2011年(8)

2010年(7)

我的朋友

分类: LINUX

2010-08-14 16:57:40

首先,我们先看看什么叫ioctl接口。从字面上看,可以说成是一种用户对设备进行控制的接口,比如说,《ldd》里说到----

大部分驱动需要 -- 除了读写设备的能力 -- 通过设备驱动进行各种硬件控制的能力. 大部分设备可进行超出简单的数据传输之外的操作; 用户空间必须常常能够请求, 例如, 设备锁上它的门, 弹出它的介质, 报告错误信息, 改变波特率, 或者自我销毁. 这些操作常常通过 ioctl 方法来支持, 它通过相同名子的系统调用来实现.


ioctl接口,是用户空间和内核空间进行交换的方式。它是一种命令形式的存在。

ioctl 驱动方法有和用户空间版本不同的原型:

 

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数. cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针. 如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的. 因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.

如果你可能想到的, 大部分 ioctl 实现包括一个大的 switch 语句来根据 cmd 参数, 选择正确的做法. 不同的命令有不同的数值, 它们常常被给予符号名来简化编码. 符号名通过一个预处理定义来安排. 定制的驱动常常声明这样的符号在它们的头文件中; scull.h 为 scull 声明它们. 用户程序必须, 当然, 包含那个头文件来存取这些符号.


选择ioctl 命令


(以下文字全部摘抄)

在编写ioctl代码之前,需要选择对应不同命令的编号。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一,这种错误匹配并不是不会发生,程序可能发现自己正在试图对FIFO和audio等这类非串行设备输入流修改波特率,如果每一个ioctl命令都是唯一的,应用程序进行这种操作时就会得到一个EINVAL错误,而不是无意间成功地完成了意想不到的操作。

要按Linux内核的约定方法为驱动程序选择ioctl编号,应该首先看看include/asm/ioctl.h和Doucumention/ioctl-number.txt这两个文件。头文件定义了要使用的位字段:类型(幻数)、序数、传送方向以及参数大小等。ioctl-number.txt文件中罗列了内核所使用的幻数,选择自己的幻数要避免和内核冲突。

#define _IOC_NRBITS 8 //序数(number)字段的字位宽度,8bits

#define _IOC_TYPEBITS 8 //幻数(type)字段的字位宽度,8bits

#define _IOC_SIZEBITS 14 //大小(size)字段的字位宽度,14bits

#define _IOC_DIRBITS 2 //方向(direction)字段的字位宽度,2bits


#define _IOC_NRMASK ((1 //序数字段的掩码,0x000000FF

#define _IOC_TYPEMASK ((1 //幻数字段的掩码,0x000000FF

#define _IOC_SIZEMASK ((1 //大小字段的掩码,0x00003FFF

#define _IOC_DIRMASK ((1 //方向字段的掩码,0x00000003


#define _IOC_NRSHIFT 0 //序数字段在整个字段中的位移,0

#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //幻数字段的位移,8

#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) //大小字段的位移,16

#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //方向字段的位移,30


/*
* Direction bits.
*/

#define _IOC_NONE 0U //没有数据传输

#define _IOC_WRITE 1U //向设备写入数据,驱动程序必须从用户空间读入数据

#define _IOC_READ 2U //从设备中读取数据,驱动程序必须向用户空间写入数据



/*
*_IOC 宏将dir,type,nr,size四个参数组合成一个cmd参数
*
*/



#define _IOC(dir,type,nr,size) \
       (((dir)
        ((type)
        ((nr)
        ((size)

/*
* used to create numbers
*/

//构造无参数的命令编号

#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
//构造从驱动程序中读取数据的命令编号


#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
//用于向驱动程序写入数据命令

#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
//用于双向传输

#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

/*
*used to decode ioctl numbers..
*/

//从命令参数中解析出数据方向,即写进还是读出

#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
//从命令参数中解析出幻数type

#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
//从命令参数中解析出序数number

#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
//从命令参数中解析出用户数据大小

#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

/* ...and for the drivers/sound files... */

#define IOC_IN (_IOC_WRITE
#define IOC_OUT (_IOC_READ
#define IOC_INOUT ((_IOC_WRITE|_IOC_READ)
#define IOCSIZE_MASK (_IOC_SIZEMASK
#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)

 

原文地址:http://blog.chinaunix.net/u3/94284/showart_1933296.html


我们也可以详细参考《ldd》中对ioctl命令:


type

魔数. 只是选择一个数(在参考了 ioctl-number.txt之后)并且使用它在整个驱动中. 这个成员是 8 位宽(_IOC_TYPEBITS).

number

序(顺序)号. 它是 8 位(_IOC_NRBITS)宽.

direction

数据传送的方向,如果这个特殊的命令涉及数据传送. 可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (数据在2个方向被传送). 数据传送是从应用程序的观点来看待的; _IOC_READ 意思是从设备读, 因此设备必须写到用户空间. 注意这个成员是一个位掩码, 因此 _IOC_READ 和 _IOC_WRITE 可使用一个逻辑 AND 操作来抽取.

size

涉及到的用户数据的大小. 这个成员的宽度是依赖体系的, 但是常常是 13 或者 14 位. 你可为你的特定体系在宏 _IOC_SIZEBITS 中找到它的值. 你使用这个 size 成员不是强制的 - 内核不检查它 -- 但是它是一个好主意. 正确使用这个成员可帮助检测用户空间程序的错误并使你实现向后兼容, 如果你曾需要改变相关数据项的大小. 如果你需要更大的数据结构, 但是, 你可忽略这个 size 成员. 我们很快见到如何使用这个成员.


结合scull来看ioctl


 

/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */

#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
 * S means "Set" through a ptr,
 * T means "Tell" directly with the argument value
 * G means "Get": reply by setting through a pointer
 * Q means "Query": response is on the return value
 * X means "eXchange": switch G and S atomically
 * H means "sHift": switch T and Q atomically
 */

#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)

#define SCULL_IOC_MAXNR 14

到现在我们知道了,自己定义的ioctl命令,一个宏对应一种操作,当我们用到ioctl方法的时候,cmd参就从用户空间传递到驱动的ioctl方法里,进行分析,之后在switch(cmd)里面做出正确的动作。


scull的icotl方法:

 

 

int scull_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{

    int err = 0, tmp;
    int retval = 0;
    
    /*
     * extract the type and number bitfields, and don't decode
     * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
     */

    if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
    if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;

    /*
     * the direction is a bitmask, and VERIFY_WRITE catches R/W
     * transfers. `Type' is user-oriented, while
     * access_ok is kernel-oriented, so the concept of "read" and
     * "write" is reversed
     */

    if (_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
    else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
    if (err) return -EFAULT;

    switch(cmd) {

     case SCULL_IOCRESET:
        scull_quantum = SCULL_QUANTUM;
        scull_qset = SCULL_QSET;
        break;
        
     case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        retval = __get_user(scull_quantum, (int __user *)arg);
        break;

     case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        scull_quantum = arg;
        break;

     case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
        retval = __put_user(scull_quantum, (int __user *)arg);
        break;

     case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
        return scull_quantum;

     case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        tmp = scull_quantum;
        retval = __get_user(scull_quantum, (int __user *)arg);
        if (retval == 0)
            retval = __put_user(tmp, (int __user *)arg);
        break;

     case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        tmp = scull_quantum;
        scull_quantum = arg;
        return tmp;
        
     case SCULL_IOCSQSET:
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        retval = __get_user(scull_qset, (int __user *)arg);
        break;

     case SCULL_IOCTQSET:
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        scull_qset = arg;
        break;

     case SCULL_IOCGQSET:
        retval = __put_user(scull_qset, (int __user *)arg);
        break;

     case SCULL_IOCQQSET:
        return scull_qset;

     case SCULL_IOCXQSET:
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        tmp = scull_qset;
        retval = __get_user(scull_qset, (int __user *)arg);
        if (retval == 0)
            retval = put_user(tmp, (int __user *)arg);
        break;

     case SCULL_IOCHQSET:
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        tmp = scull_qset;
        scull_qset = arg;
        return tmp;

        /*
         * The following two change the buffer size for scullpipe.
         * The scullpipe device uses this same ioctl method, just to
         * write less code. Actually, it's the same driver, isn't it?
         */


     case SCULL_P_IOCTSIZE:
        scull_p_buffer = arg;
        break;

     case SCULL_P_IOCQSIZE:
        return scull_p_buffer;


     default: /* redundant, as cmd was checked against MAXNR */
        return -ENOTTY;
    }
    return retval;

}



阅读(1524) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~