嵌入式Linux热门培训内容之Linux 驱动之Ioctl

时间:2018-12-13 17:35:38

一、在用户空间,使用ioctl系统调用来控制设备,原型如下:

int ioctl(int fd,unsigned long cmd,...);

/*fd:文件描述符 cmd:控制命令

...:可选参数:插入*argp,具体内容依赖于cmd*/

二、驱动ioctl方法:

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

/*inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。 cmd 由用户空间直接不经修改的传递给驱动程序

arg 可选。*/

  在驱动程序中实现的ioctl函数体内,实际上是有一个switch {case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。

在Linux核心中是这样定义一个命令码的:

____________________________________

| 设备类型  | 序列号 |  方向 | 数据尺寸 |

|----------|--------|------|-------- |

| 8 bit   |  8 bit   | 2 bit |8~14 bit|

|----------|--------|------|-------- |

  这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

三、使用命令定义ioctl幻数

  内核提供了一些宏来帮助定义命令:

//nr为序号,datatype为数据类型,如int

_IO(type, nr ) //没有参数的命令

_IOR(type, nr, datatype) //从驱动中读数据

_IOW(type, nr, datatype) //写数据到驱动

_IOWR(type,nr, datatype) //双向传送

  定义命令例子:

#define MEM_IOC_MAGIC 'm' //定义类型

#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)

#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)

四、实例分析

驱动层:

#include <linux/init.h>

#include <linux/module.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/device.h>

#include <linux/module.h>

#include <linux/types.h>

#include <linux/errno.h>

#include <linux/mm.h>

#include <linux/sched.h>

#include <asm/io.h>

#include <asm/system.h>

#include <asm/uaccess.h>

/* 定义幻数 */

#define MEMDEV_IOC_MAGIC  'k'

/* 定义命令 */

#define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)

#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)  /*获取数据*/

#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)  /*设置数据*/

#define MEMDEV_IOC_MAXNR 3   /*一共定义了三条命令*/

MODULE_LICENSE("WQFDual BSD/GPL");

MODULE_AUTHOR("WQFDriver Monkey");

struct mmap_dev_t

{

    dev_t dev_num;

    char *dev_name;

    struct class* cls;

    char * cls_name;

    struct cdev cdev;

};

struct mmap_dev_t mmap_dev = 

{

    .dev_name = "test_dev",

    .cls_name = "test_class",

};

int my_open(struct inode *inode, struct file *filp)

{

    printk(KERN_INFO"WQFmy_open()++\n");

    printk(KERN_INFO"WQFmy_open()--\n");

    printk("wQF===============================\n");

    return 0;

}

ssize_t my_read(struct file *filp, char *buff, size_t count, loff_t *f_pos)

{

    printk(KERN_INFO"my_read()++\n");

    printk(KERN_INFO"my_read()--\n");

    return 0;

}

ssize_t my_write(struct file *filp, const char *buff, size_t count, loff_t *f_pos)

{

    printk(KERN_INFO"buff is :\n %s", buff);

    printk(KERN_INFO"my_write()--\n");

    return count;

}

int my_ioctl(struct inode *inode, struct file *filp,  \

                 unsigned int cmd, unsigned long arg)   /*IOCTL的驱动函数*/

{

        int err = 0;

    int ret = 0;

    int ioarg = 0;

    /* 检测命令的有效性 */

    if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)   /*检测是不是我们声明的幻数*/

        return -EINVAL;

    if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)    /*检测是不是超出了我们的命令个数*/ 

        return -EINVAL;

    /* 根据命令类型,检测参数空间是否可以访问 */

    if (_IOC_DIR(cmd) & _IOC_READ)

        err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));

    else if (_IOC_DIR(cmd) & _IOC_WRITE)

        err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));

    if (err) 

        return -EFAULT;

    /* 根据命令,执行相应的操作 */

    switch(cmd) {

      /* 打印当前设备信息 */

      case MEMDEV_IOCPRINT:

          printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");

        break;

      /* 获取参数 */

      case MEMDEV_IOCGETDATA: 

        ioarg = 1101;

        ret = __put_user(ioarg, (int *)arg);

        break;

      /* 设置参数 */

      case MEMDEV_IOCSETDATA: 

        ret = __get_user(ioarg, (int *)arg);

        printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);

        break;

      default:  

        return -EINVAL;

    }

    return ret;    

}

struct file_operations mmap_opt = 

{

    .owner = THIS_MODULE,

    .open = my_open,

    .read = my_read,

    .write = my_write,

    .unlocked_ioctl  = my_ioctl,

    /* linux2.6.29和linux2.6.38的内核在file_operations结构发生了变化,

    否则在linux2.6.38内核中,继续使用.ioctl成员,编译时就会报错:

    error: unknown field 'ioctl' specified in initializer,struct file_operations结构体定义在include/linux/fs.h文件中。*/

};

static int wqfmmap_init(void)

{

    int err = 0;

    printk("mmap_init()++\n");

    //dynamic alloc device number

    if(0 != (err = alloc_chrdev_region

            (&mmap_dev.dev_num, 0,3, mmap_dev.dev_name)))  /*自动分配主次设备号*/

        goto alloc_chrdev_region_err;

    printk("Wmajor = %d, Wminor = %d\n",

           MAJOR(mmap_dev.dev_num),MINOR(mmap_dev.dev_num));/*从设备编号里抽取出主次设备号*/

    //create class

    mmap_dev.cls = class_create(THIS_MODULE,mmap_dev.cls_name);

    if(IS_ERR(mmap_dev.cls))

        goto class_create_err;

    //create character device

    device_create(mmap_dev.cls,NULL,mmap_dev.dev_num,&mmap_dev,"wfdevice");/*创建字符设备 在dev/下面会生成这个设备文件*/

    //activate character driver

    cdev_init(&mmap_dev.cdev, &mmap_opt);/*file_operations 字符设备操作指针*/

    mmap_dev.cdev.owner = THIS_MODULE;

    err = cdev_add(&mmap_dev.cdev,mmap_dev.dev_num,1);

    if(err)

        goto cdev_add_err;

    printk("mmap_init()--\n");

    return 0;

cdev_add_err:    

    device_destroy(mmap_dev.cls, mmap_dev.dev_num);

    class_destroy(mmap_dev.cls);

class_create_err:

    unregister_chrdev_region(mmap_dev.dev_num,3);

alloc_chrdev_region_err:

    return (-1);

}

static void wqfmmap_exit(void)

{

    printk(KERN_INFO"mmap_exit()++\n");

    cdev_del(&mmap_dev.cdev);

    device_destroy(mmap_dev.cls, mmap_dev.dev_num);

    class_destroy(mmap_dev.cls);

    unregister_chrdev_region(mmap_dev.dev_num,3);

    printk(KERN_INFO"mmap_exit()--\n");

}

module_init(wqfmmap_init);

module_exit(wqfmmap_exit);

应用层:

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/ioctl.h>

#include <unistd.h> /*加入这个头 close才不会出错*/

#include "memdev.h"  /* 包含命令定义 */

int main()

{

    int fd = 0;

    int cmd;

    int arg = 0;

    char Buf[4096];

    /*打开设备文件*/

    fd = open("/dev/wfdevice",O_RDWR);

    if (fd < 0)

    {

        printf("Open Dev Mem0 Error!\n");

        return -1;

    }

    /* 调用命令MEMDEV_IOCPRINT */

    printf("<--- Call MEMDEV_IOCPRINT --->\n");

    cmd = MEMDEV_IOCPRINT;

    if (ioctl(fd, cmd, &arg) < 0)

        {

            printf("Call cmd MEMDEV_IOCPRINT fail\n");

            return -1;

    }

    /* 调用命令MEMDEV_IOCSETDATA */

    printf("<--- Call MEMDEV_IOCSETDATA --->\n");

    cmd = MEMDEV_IOCSETDATA;

    arg = 2007;

    if (ioctl(fd, cmd, &arg) < 0)

        {

            printf("Call cmd MEMDEV_IOCSETDATA fail\n");

            return -1;

    }

    /* 调用命令MEMDEV_IOCGETDATA */

    printf("<--- Call MEMDEV_IOCGETDATA --->\n");

    cmd = MEMDEV_IOCGETDATA;

    if (ioctl(fd, cmd, &arg) < 0)

        {

            printf("Call cmd MEMDEV_IOCGETDATA fail\n");

            return -1;

    }

    printf("<--- In User Space MEMDEV_IOCGETDATA Get Data is %d --->\n\n",arg);    

    close(fd);

    return 0;    

}

#ifndef _MEMDEV_H_

#define _MEMDEV_H_

#include "linux/ioctl.h"

#ifndef MEMDEV_MAJOR

#define MEMDEV_MAJOR 0   /*预设的mem的主设备号*/

#endif

#ifndef MEMDEV_NR_DEVS

#define MEMDEV_NR_DEVS 2    /*设备数*/

#endif

#ifndef MEMDEV_SIZE

#define MEMDEV_SIZE 4096

#endif

/*mem设备描述结构体*/

struct mem_dev                                     

{                                                        

  char *data;                      

  unsigned long size;       

};

/* 定义幻数 */

#define MEMDEV_IOC_MAGIC  'k'

/* 定义命令 */

#define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)

#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)

#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)

#define MEMDEV_IOC_MAXNR 3

#endif /* _MEMDEV_H_ */

注意注意!!

有时候会发现ioctl没有调用到驱动层

注意下面的问题

1、linux-2.6.36 后由unlocked_ioctl 代替了ioctl

2、在你的机器下面运行 uname -a 

127|rk3399_mid:/ # uname -a Linux localhost 4.4.83

 #111 SMP PREEMPT Thu Mar 29 23:54:51 EDT 2018 aarch64 

说明你的是64位系统 所以要用compat_ioctl 代替unlocked_ioctl