• linux驱动开发笔记_ioctl函数


    1.相关概念

    ioctl 是设备驱动程序中设备控制接口函数。某些设备除了打开、关闭、读出和写入功能外,可能还有其它的功能,比如说设置串口波特率、设置马达的转速等等。

    1.用户空间函数

    #include <sys/ioctl.h>
    int ioctl (int fd, unsigned int cmd, ...)
    1
    2
    参数 描述
    fd 打开文件描述符
    cmd 交互协议,设备驱动将根据cmd执行相应的操作
    … 可变参数arg,依赖cmd中指定的长度以及类型
    ioctl()函数执行成功之后则会返回0;失败的话就会返回 -1;
    2.驱动程序函数

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    1
    2
    unlocked_ioctl,顾名思义,应该在无大内核锁(BKL)的情况下调用;
    compat 全称 compatible(兼容的),主要目的是为 64 位系统提供 32 位 ioctl 的兼容方法,也是在无大内核锁的情况下调用。
    其中第二个参数是 cmd 从用户空间无改变的传过来的,第三个参数是传过来的用户空间的可寻址地址;

    3. ioctl中的cmd(交互协议)

    交互协议,就是用户空间和驱动程序之间达成的一致性协议。需要完成什么功能都在这个协议中得到包含。cmd是一个32位的数据,其中不同的位置代表了不同的段。

    在内核当中,有对于生成ioctl命令相关的宏,可以很方便直接生成cmd:

    #define _IOC(dir,type,nr,size) \
    (((dir) << _IOC_DIRSHIFT) | \
    ((type) << _IOC_TYPESHIFT) | \
    ((nr) << _IOC_NRSHIFT) | \
    ((size) << _IOC_SIZESHIFT))

    #define _IOC_NRSHIFT 0
    #define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
    #define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
    #define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)

    #define _IOC_NRBITS 8
    #define _IOC_TYPEBITS 8
    # define _IOC_SIZEBITS 14

    1.dir(direction), ioctl命令的访问模式。定义数据的传输方向。可以为 _IOC_NONE 、 _IOC_READ、 _IOC_WRITE、 _IOC_READ|_IOC_WRITE。分别代表了无数据、读数据、写数据、读写数据。读写是从用户空间来看的,写是往驱动写,读是往用户读。
    2.type(device type),设备类型,有些地方叫做 幻数 。可以为任意的char类型的字符。比如说设置为 ’a‘ , ‘b’ , ’ D’等等。主要作用是使 ioctl 命令有唯一的设备标识;
    3. nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
    4. size涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;

    为了更加方便的使用,在上述宏定义的基础上又衍生出了更加方便的ioctl命令:

    #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
    #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
    #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
    #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
    1
    2
    3
    4
    _IO: 定义不带参数的 ioctl 命令
    _IOW: 定义带写参数的 ioctl 命令(copy_from_user)
    _IOR: 定义带读参数的ioctl命令(copy_to_user)
    _IOWR: 定义带读写参数的 ioctl 命令
    在内核驱动代码中,还提供了分离出不同的段的宏定义接口,供内核的驱动代码使用。

    #define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
    #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
    #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
    #define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
    1
    2
    3
    4
    比如:
    if( _IOC_TYPE(cmd) != ‘c’) 判断是否设备种类正确
    if( _IOC_NR(cmd) > 2) 判断命令的数值是不是大于2
    if( _IOC_DIR(cmd) & _IOC_READ) 判断该命令是否为可读命令
    4.ioctl例子分析

    这个例子用ioctl来实现内核空间和用户空间的数据交互。
    1.定义用户程序和驱动程序共同定义的协商文件ioctl_menu.h。

    #ifndef __IOCTL_MENU_
    #define __IOCTL_MENU_

    #include <linux/ioctl.h> // 内核空间

    /*定义设备类型*/
    #define IOC_MAGIC 'c'

    /* 初始化设备*/
    #define IOCINIT _IO(IOC_MAGIC, 0)

    /* 写寄存器 */
    #define IOCWREG _IOW(IOC_MAGIC, 1, int)

    /* 读寄存器 */
    #define IOCRREG _IOR(IOC_MAGIC, 2, int)

    #define IOC_MAXNR 2

    #endif

    该文件中定义了三个操作方式: 初始化、读出数据、写入数据。
    2.设备驱动程序代码

    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/ide.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/errno.h>
    #include <linux/gpio.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <linux/of.h>
    #include <linux/of_address.h>
    #include <linux/of_gpio.h>
    #include <linux/semaphore.h>
    #include <linux/timer.h>
    #include <linux/of_irq.h>
    #include <linux/irq.h>
    #include <linux/wait.h>
    #include <linux/poll.h>
    #include <asm/mach/map.h>
    #include <asm/uaccess.h>
    #include <asm/io.h>

    #include "ioctl_menu.h"

    struct ioctl_struct{

    dev_t devid; /* 设备号*/
    int major; /* 主设备号*/
    int minor; /* 次设备号 */

    struct cdev cdev; /* cdev */
    struct class *class; /* 类 */
    struct device *device; /* 设备 */

    int data; /*设备的一个储存空间*/
    };

    struct ioctl_struct mytest;


    long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
    int ret;

    //如果设备种类不正确
    if(_IOC_TYPE(cmd) != IOC_MAGIC)
    {
    pr_err("[%s] command type [%c] error!\n", __func__, _IOC_TYPE(cmd));
    return -1;
    }

    //检查序号是否超限
    if(_IOC_NR(cmd) > IOC_MAXNR )
    {
    pr_err("[%s] command number [%d] exceeded!\n", __func__, _IOC_NR(cmd));
    return -1;
    }

    //检查访问模式
    if(_IOC_DIR(cmd) & _IOC_READ)
    {
    ret= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
    }
    else if (_IOC_DIR(cmd) & _IOC_WRITE)
    {
    ret= !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
    }
    if(ret) return -1;

    switch(cmd)
    {
    //初始化
    case IOCINIT:
    printk(KERN_ALERT "hello ,i am a register!\n");
    break;

    //写数据
    case IOCWREG:
    ret = copy_from_user(&mytest.data, (int __user *)arg, sizeof(int));
    break;

    //读数据
    case IOCRREG:
    ret = copy_to_user((int __user *)arg, &mytest.data, sizeof(int));
    break;

    default: break;
    }
    return 0;
    }


    static struct file_operations mytest_fops =
    {
    .owner = THIS_MODULE,
    .unlocked_ioctl = test_ioctl,

    };

    static int __init ioctl_init(void)
    {
    //1.构建设备号
    if(mytest.major)
    {
    mytest.devid = MKDEV(mytest.major, 0);
    register_chrdev_region(mytest.devid , 1, "mytest");
    }
    else
    {
    alloc_chrdev_region(&mytest.devid, 0, 1, "mytest");
    mytest.major = MAJOR(mytest.devid);
    mytest.minor = MINOR(mytest.devid);
    }

    //2.注册字符设备
    cdev_init(&mytest.cdev , &mytest_fops);
    cdev_add(&mytest.cdev , mytest.devid, 1);

    //3.创建类
    mytest.class = class_create(THIS_MODULE, "mytest");

    //4.创建设备文件
    mytest.device = device_create(mytest.class, NULL, mytest.devid, NULL, "mytest");

    return 0;
    }


    static void __exit ioctl_exit(void)
    {
    printk(KERN_ALERT "ioctl Goodbye!\n");

    cdev_del(&mytest.cdev);
    unregister_chrdev_region(mytest.devid, 1);
    device_destroy(mytest.class, mytest.devid);
    class_destroy(mytest.class);
    }


    module_init(ioctl_init);
    module_exit(ioctl_exit);

    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("linyuwang");



    其中的access_ok()函数原型如下,用来判断用户空间的地址是否可读或者可以写。
    int access_ok (int type, const void *addr, unsigned long size);
    1
    3.用户测试代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>

    #include "ioctl_menu.h"

    #define READ 0
    #define WRITE 1


    int main(int argc , char *argv[])
    {
    int fd;
    int method;
    int data;
    int ret;

    //判断用法是否正确
    if(argc < 3)
    {
    printf("error usage!\r\n");
    return -1;
    }

    //打开设备文件
    fd = open(argv[1], O_RDWR);
    if(fd < 0)
    {
    printf("file %s open failed!\r\n", argv[1]);
    return -2;
    }

    //判断是读还是写
    if(memcmp(argv[2], "read", sizeof("read")) == 0)
    {
    method = READ;
    }
    else if(memcmp(argv[2], "write", sizeof("write")) == 0)
    {
    method = WRITE;
    }
    else
    {
    printf("methord error!");
    return -3;
    }

    //完成对应的操作
    if(method == READ)
    {
    ret = ioctl(fd, IOCRREG, &data);
    if(ret == 0)
    {
    printf("%d\r\n", data);
    }
    else
    {
    printf("Read failed!");
    return -4;
    }
    }
    else if(method == WRITE)
    {
    data = atoi(argv[3]);

    ret = ioctl(fd, IOCWREG, &data);
    if(ret == 0)
    {
    printf("Success!");
    }
    else
    {
    printf("Write failed!");
    return -5;
    }
    }
    return 0;
    }
    ————————————————
    版权声明:本文为CSDN博主「一人一城506」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/weixin_43625081/article/details/113389278

  • 相关阅读:
    Markdown语法
    window环境下获取python安装的路径
    JS 获取当天所在月的第一天的日期,最后一天日期,所在周的每天的日期,时间,所在每月日期,时间的计算
    Vue中父组件向子组件echarts传值问题
    echarts修改X、 Y坐标轴字体的颜色
    Ultibo--树莓派嵌入式系统开发工具
    mORMot使用synDBDataSet时字段类型不正确引起的问题
    UNIGUI接收普通消息和被动回复用户消息
    unigui验证微信服务器的有效性
    unigui不是单个网页相应的链接,而是整体Web Application,如何把webApp的子功能映射到微信公众号菜单?
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/16336298.html
Copyright © 2020-2023  润新知