Linux设备驱动程序——高级字符驱动程序操作(ioctl)

2430阅读 0评论2014-01-07 wangtisheng
分类:嵌入式

                                        Linux设备驱动程序
                                  ——高级字符驱动程序操作(ioctl) 
一、ioctl
    除了读取和写入设备之外,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。这些操作通常通过ioctl方法支持。
    在用户空间,ioctl系统调用具有如下原型:
        int ioctl(int d, int request, ...);
    驱动程序的ioctl方法原型:
        int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
    实现ioctl的方法:①定义命令,②实现命令
二、选择ioctl命令

    
在编写ioctl代码之前,需要选择对应不同命令的编号,为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一
为方便程序员创建唯一的ioctl命令号,每一个命令号被分为许多个位字段。定义号码的新方法使用4个位字段,其符号定义在>中。
        type       -- 幻数,表明哪个设备的命令,8位宽
        number     -- 序数,表明设备命令中的第几个,8位宽
        direction  -- 数据传送的方向,可能的值是_IOC_NONE,_IOC_READ, _IOC_WRITE。数据传送是从应用程序的观点来看待的,_IOC_READ 意思是从设备读。
        size       -- 用户数据的大小
    内核提供了一些构造命令编号的宏,在中:
        _IO(type,nr)            -- 构造无参数的命令编号
        _IOR(type,nr,datatype)  -- 构造从驱动程序中读取数据的命令编号
        _IOW(type,nr,datatype)  -- 用于写入数据的命令
        _IOWR(type,nr,datatype) -- 用于双向传输
    内核提供了解开以上位字段的宏
        _IOC_DIR(nr)
        _IOC_TYPE(nr)
        _IOC_NR(nr)
        _IOC_SIZE(nr)
    一些命令示例: 

点击(此处)折叠或打开

  1. /*
  2.  * Ioctl definitions
  3.  */

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

  7. #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)

  8. /*
  9.  * S means "Set" through a ptr,
  10.  * T means "Tell" directly with the argument value
  11.  * G means "Get": reply by setting through a pointer
  12.  * Q means "Query": response is on the return value
  13.  * X means "eXchange": switch G and S atomically
  14.  * H means "sHift": switch T and Q atomically
  15.  */
  16. #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
  17. #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
  18. #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
  19. #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
  20. #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
  21. #define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
  22. #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
  23. #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
  24. #define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
  25. #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
  26. #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
  27. #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)

  28. /*
  29.  * The other entities only have "Tell" and "Query", because they're
  30.  * not printed in the book, and there's no need to have all six.
  31.  * (The previous stuff was only there to show different ways to do it.
  32.  */
  33. #define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13)
  34. #define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14)
  35. /* ... more to come */

  36. #define SCULL_IOC_MAXNR 14      

三、ioctl的返回值

    如果使用了不合适的 ioctl 命令号,应当返回-ENOTTY 。这个错误码被 C 库解释为"不合适的设备 ioctl。然而,它返回-EINVAL仍是相当普遍的。
四、使用ioctl参数(附加参数)
    如果附加参数为整数,直接使用即可。如果是指针,必须确保这个用户地址是有效的,因此使用前需进行正确的检查。通过函数access_ok验证地址(而不传输数据),在中声明:
    int access_ok(int type,const void *addr,unsigned long size);
        type    -- 应该是VERIFY_READ或者VERIFY_WRITE,取决于要执行的动作时读取还是写入用户空间内存区。
        addr    -- 用户空间地址
        size    -- 字节数  

点击(此处)折叠或打开

  1.     int err = 0, tmp;
  2.     int retval = 0;
  3.     
  4.     /*
  5.      * extract the type and number bitfields, and don't decode
  6.      * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
  7.      */
  8.     if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
  9.     if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;

  10.     /*
  11.      * the direction is a bitmask, and VERIFY_WRITE catches R/W
  12.      * transfers. `Type' is user-oriented, while
  13.      * access_ok is kernel-oriented, so the concept of "read" and
  14.      * "write" is reversed
  15.      */
  16.     if (_IOC_DIR(cmd) & _IOC_READ)
  17.         err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
  18.     else if (_IOC_DIR(cmd) & _IOC_WRITE)
  19.         err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
  20.     if (err) return -EFAULT;
    在调用access_ok之后,驱动程序就可以安全地进行实际的数据传输了。除了copy_from_user和copy_to_user函数外,还可以使用如下已经为常用的数据大小优化过的一组函数:
    ① 写数据到用户空间
        put_user(datum,ptr)
        __put_user(datum,ptr)
    它们相对比较快,当要传递单个数据时,应该用这些宏,而不是copy_to_user。put_user进行检查以确保进程可以写入指定的内存地址。__put_user做的检查少些,应该在已经使用access_ok校验过再使用。
   
从用户空间接收数据

        get_user(local, ptr) 
        __get_user(local, ptr)
    __get_user应该在操作地址已被access_ok校验过再使用。
五、ioctl示例
    该例子来自国嵌,ioctl.rar
 

上一篇:Linux设备驱动程序--并发和竞态(信号量和原子操作)
下一篇:Linux设备驱动程序——高级字符驱动程序操作(阻塞型I/O)