【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十六章 设备驱动IO控制

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)


第五十六章 设备驱动IO控制

本章导读

本章节我们将来学习ioctl接口,那么我们在学习之前先来回想一下,我们以前操作一个GPIO是怎么操作的呢?在前面章节中我们使用GPIO函数来操作GPIO的,在使用GPIO函数之前是使用寄存器来进行操作的,我们使用GPIO函数操作GPIO比直接使用寄存器来操作GPIO进一步升级了。我们操作GPIO的时候,使用的是逻辑判断,kbuf0的数据是从应用层传递过来的我们要使用到copy_from_usr。当我们在应用层write驱动节点的时候,会触发驱动层的write函数,然后我们在write函数里面控制我们的GPIO。虽然这样的方法是可以的,但是这样的方法是不是比较麻烦呢?有没有更简单的办法呢?当然是有了,方法就是我们这节课讲的ioctl接口。

56.1章节讲解了设备驱动IO控制原理

56.2章节编写驱动程序和应用程序,测试应用层不传递数据

56.3章节编写驱动程序和应用程序,测试应用层写数据

56.4章节编写驱动程序和应用程序,测试应用层读数据

本章内容对应视频讲解链接(在线观看):

ioctl接口(一) → https://www.bilibili.com/video/BV1Vy4y1B7ta?p=32

ioctl接口(二) → https://www.bilibili.com/video/BV1Vy4y1B7ta?p=33

程序源码在网盘资料"iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\013-设备驱动io控制"路径下。

56 .1 设备驱动IO控制原理

在内核3.0 以前,ioctl接口的名字叫ioctl;内核3.0以后,ioctl接口的名字叫unlocked_ioctl。unlocked_ioctl就是ioctl接口,但是功能和对应的系统调用均没有发生变化。unlocked_ioctl和read/write函数有什么异同呢?相同点:都可以往内核写数据。不同点:read函数只能完成读的功能,write只能完成写的功能。读取大数据的时候效率高。ioctl既可以读也可以写,读取大数据的时候效率不高。

我们现在有了ioctl函数,内核已经把工作的任务给我们区分了,定义命令就不再使用read和write函数了,而是使用ioctl函数,因为ioctl函数的任务就是对我们的工作参数进行设置和查询,write和read函数专注于数据传输。

在驱动程序里, ioctl()函数上传送的变量 cmd是应用程序用于区别设备驱动程序请求处理内容的值。cmd除了可区别数字外,还包含有助于处理的几种相应信息。 cmd的大小为 32位,共分 4 个域,命令规则为:

第一个分区:0-7,命令的编号,范围是0-255.

第二个分区:8-15,命令的幻数。

第一个分区和第二个分区主要作用是用来区分命令的。

第三个分区:16-29表示传递的数据大小。

第四个分区:30-31 代表读写的方向。

00:表示用户程序和驱动程序没有数据传递

10:表示用户程序从驱动里面读数据

01:表示用户程序向驱动里面写数据

11:先写数据到驱动里面然后在从驱动里面把数据读出来。

合成宏:

_IO(type,nr) :用来定义没有数据传递的命令

_IOR(type,nr,size) :用来定义从驱动中读取数据的命令

_IOW(type,nr,size) :用来定义向驱动写入数据的命令

_IOWR(type,nr,size) :用来定义数据交换类型的命令,先写入数据,再读取数据这类命令。

参数说明:

  • type:表示命令组成的魔数,也就是8~15位
  • nr:表示命令组成的编号,也就是0~7位
  • size:表示命令组成的参数传递大小,注意这里不是传递数字,而是数据类型,如要传递4字节,就可以写成int。

分解宏:

_IOC_DIR(nr) :分解命令的方向,也就是上面说30~31位的值

_IOC_TYPE(nr) :分解命令的魔数,也就是上面说8~15位的值

_IOC_NR(nr) :分解命令的编号,也就是上面说0~7位

_IOC_SIZE(nr) :分解命令的复制数据大小,也就是上面说的16~29位

参数说明:

  • nr :要分解的命令

验证程序代码如下所示:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define CMD_TEST0 _IO('L', 0)
#define CMD_TEST1 _IO('A', 1)
#define CMD_TEST2 _IOW('L', 2, int)
#define CMD_TEST3 _IOR('L', 3, int)

int main(int argc, char *argv[])
{
    printf("30-31 is %d \n", _IOC_DIR(CMD_TEST0));
    printf("30-31 is %d \n", _IOC_DIR(CMD_TEST3));

    printf("7-15 is %c \n", _IOC_TYPE(CMD_TEST0));
    printf("7-15 is %c \n", _IOC_TYPE(CMD_TEST1));

    printf("0-7 is %d \n", _IOC_NR(CMD_TEST2));
}

那么我们定义的这些命令要怎么用呢?第一种方法是用分解宏将它分解出来,还有一种用法是搭配switch来用。

56 . 2 编写测试-不传递数据

程序源码在网盘资料"iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\013-设备驱动io控制\001"路径下。

我们拷贝第42章的驱动代码,在此基础上进行修改,完整代码如下所示:

cpp 复制代码
//初始化头文件
#include <linux/init.h>
//最基本的文件,支持动态添加和卸载模块。
#include <linux/module.h>
//包含了miscdevice结构的定义及相关的操作函数。
#include <linux/miscdevice.h>
//文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/fs.h>
//包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/uaccess.h>
//包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/io.h>
//驱动要写入内核,与内核相关的头文件
#include <linux/kernel.h>

#define CMD_TEST1 _IO('A', 1)
#define CMD_TEST0 _IO('L', 0)

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    printk("misc_read\n ");
    return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    /*应用程序传入数据到内核空间,然后控制led的逻辑,在此添加*/
    // kbuf保存的是从应用层读取到的数据
    char kbuf[64] = {0};
    // copy_from_user 从应用层传递数据给内核层
    if (copy_from_user(kbuf, ubuf, size) != 0)
    {
        // copy_from_user 传递失败打印
        printk("copy_from_user error \n ");
        return -1;
    }
    //打印传递进内核的数据
    printk("kbuf is %d\n ", kbuf[0]);
    return 0;
}
/****************************************************************************************
 * @brief misc_release : 用户空间关闭设备节点时执行此函数
 * @param inode        : 文件索引
 * @param file         : 文件
 * @return             :成功返回 0  
 ****************************************************************************************/
int misc_release(struct inode *inode, struct file *file)
{
    printk("hello misc_relaease bye bye \n ");
    return 0;
}
/****************************************************************************************
 * @brief misc_open : 用户空间打开设备节点时执行此函数
 * @param inode     : 文件索引
 * @param file      : 文件
 * @return          : 成功返回 0    
 ****************************************************************************************/
int misc_open(struct inode *inode, struct file *file)
{
    printk("hello misc_open\n ");
    return 0;
}
/****************************************************************************************
 * @brief misc_ioctl : 用户空间使用ioctl(int fd, unsigned long request, ...(void* arg))时
 *                      自动执行此函数,根据命令执行对应的操作
 * @param file       : 设备文件
 * @param cmd        : 用户空间ioctl接口命令request
 * @param value      : 用户空间的arg指针,依赖于接口命令request
 * @return           : 成功返回 0   
 ****************************************************************************************/
long misc_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{
    printk("LED ON \n");
    switch (cmd) //根据命令进行对应的操作
    {
    case CMD_TEST0:
        printk("LED ON \n");
        break;
    case CMD_TEST1:
        printk("LED OFF \n");
        break;
    }
    return 0;
}
struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
    .unlocked_ioctl = misc_ioctl /* 64 bit system special */};
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops,
};
static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error \n");
    }
    printk("misc registe is succeed \n");

    return 0;
}
static void misc_exit(void)
{
    misc_deregister(&misc_dev);

    printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

参考[++++第三十七章 Linux内核模块++++](#第三十七章 Linux内核模块),将led.c编译为led.ko 文件,如下图所示:

应用程序ioctrl.c:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IO('A',1)
//#define CMD_TEST2 _IOW('L',2,int)
//#define CMD_TEST3 _IOR('L',3,int)

int main(int argc,char *argv[])
{
    int fd;
    char buf[64] ={0};
    fd = open("/dev/hello_misc",O_RDWR);
    if(fd < 0)
    {
    perror("open error \n");
    return fd;  
    }
    while(1)
    {
    ioctl(fd,CMD_TEST0);
    sleep(2);
    ioctl(fd,CMD_TEST1);
    }
    
    return 0;
}

我们将ioctl.c文件拷贝到Ubuntu的/home/topeet/imx8mm/13/001目录下,输入以下命令编译ioctl.c

我们进入共享目录,加载驱动模块以后,再运行应用程序,如下图所示:

56 .3 编写测试-写数据

程序源码在网盘资料"iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\013-设备驱动io控制\002"路径下。

我们在56.2章节代码的基础上进行修改,完整代码如下所示:

cpp 复制代码
//初始化头文件
#include <linux/init.h>
//最基本的文件,支持动态添加和卸载模块。
#include <linux/module.h>
//包含了miscdevice结构的定义及相关的操作函数。
#include <linux/miscdevice.h>
//文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/fs.h>
//包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/uaccess.h>
//包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/io.h>
//驱动要写入内核,与内核相关的头文件
#include <linux/kernel.h>

#define CMD_TEST1 _IO('A', 1)
#define CMD_TEST0 _IO('L', 0)
#define CMD_TEST2 _IOW('L', 2, int)
#define CMD_TEST3 _IOW('L', 3, int)

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    printk("misc_read\n ");
    return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    /*应用程序传入数据到内核空间,然后控制led的逻辑,在此添加*/
    // kbuf保存的是从应用层读取到的数据
    char kbuf[64] = {0};
    // copy_from_user 从应用层传递数据给内核层
    if (copy_from_user(kbuf, ubuf, size) != 0)
    {
        // copy_from_user 传递失败打印
        printk("copy_from_user error \n ");
        return -1;
    }
    //打印传递进内核的数据
    printk("kbuf is %d\n ", kbuf[0]);
    return 0;
}
/****************************************************************************************
 * @brief misc_release : 用户空间关闭设备节点时执行此函数
 * @param inode        : 文件索引
 * @param file         : 文件
 * @return             :成功返回 0  
 ****************************************************************************************/
int misc_release(struct inode *inode, struct file *file)
{
    printk("hello misc_relaease bye bye \n ");
    return 0;
}
/****************************************************************************************
 * @brief misc_open : 用户空间打开设备节点时执行此函数
 * @param inode     : 文件索引
 * @param file      : 文件
 * @return          : 成功返回 0    
 ****************************************************************************************/
int misc_open(struct inode *inode, struct file *file)
{
    printk("hello misc_open\n ");
    return 0;
}
/****************************************************************************************
 * @brief misc_ioctl : 用户空间使用ioctl(int fd, unsigned long request, ...(void* arg))时
 *                      自动执行此函数,根据命令执行对应的操作
 * @param file       : 设备文件
 * @param cmd        : 用户空间ioctl接口命令request
 * @param value      : 用户空间的arg指针,依赖于接口命令request
 * @return           : 成功返回 0   
 ****************************************************************************************/
long misc_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{

    switch (cmd)//根据命令进行对应的操作
    {
    case CMD_TEST2:
        printk("LED ON \n");
        printk("value is %ld\n", value);
        break;
    case CMD_TEST3:
        printk("LED OFF \n");
        printk("value is %ld\n", value);
        break;
    }
    return 0;
}
struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
    .unlocked_ioctl = misc_ioctl };
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops,
};
static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error \n");
    }
    printk("misc registe is succeed \n");

    return 0;
}
static void misc_exit(void)
{
    misc_deregister(&misc_dev);

    printk(" misc goodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

将led.c编译为led.ko 文件,如下图所示:

应用程序代码ioctrl.c代码如下:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IO('A',1)

#define CMD_TEST2 _IOW('L',2,int)
#define CMD_TEST3 _IOW('L',3,int)
//#define CMD_TEST3 _IOR('L',3,int)

int main(int argc,char *argv[])
{
    int fd;
    char buf[64] ={0};
    fd = open("/dev/hello_misc",O_RDWR);
    if(fd < 0)
    {
    perror("open error \n");
    return fd;  
    }
    while(1)
    {
    ioctl(fd,CMD_TEST2,0);
    sleep(2);
    ioctl(fd,CMD_TEST3,1);
    }
    
    return 0;
}

编译完成如下图所示:

我们进入共享目录,卸载原来加载的驱动模块,然后再加载新的驱动模块以后,再运行应用程序,如下图所示:

56 . 4 编写测试-读数据

程序源码在网盘资料"iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\013-设备驱动io控制\003"路径下。

我们在56.3的代码的基础上进行修改,完整代码如下所示:

cpp 复制代码
//初始化头文件
#include <linux/init.h>
//最基本的文件,支持动态添加和卸载模块。
#include <linux/module.h>
//包含了miscdevice结构的定义及相关的操作函数。
#include <linux/miscdevice.h>
//文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/fs.h>
//包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/uaccess.h>
//包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/io.h>
//驱动要写入内核,与内核相关的头文件
#include <linux/kernel.h>

#define CMD_TEST1 _IO('A', 1)
#define CMD_TEST0 _IO('L', 0)
#define CMD_TEST2 _IOW('L', 2, int)
#define CMD_TEST3 _IOW('L', 3, int)
#define CMD_TEST4 _IOR('L', 4, int)

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    printk("misc_read\n ");
    return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    /*应用程序传入数据到内核空间,然后控制led的逻辑,在此添加*/
    // kbuf保存的是从应用层读取到的数据
    char kbuf[64] = {0};
    // copy_from_user 从应用层传递数据给内核层
    if (copy_from_user(kbuf, ubuf, size) != 0)
    {
        // copy_from_user 传递失败打印
        printk("copy_from_user error \n ");
        return -1;
    }
    //打印传递进内核的数据
    printk("kbuf is %d\n ", kbuf[0]);
    return 0;
}
/****************************************************************************************
 * @brief misc_release : 用户空间关闭设备节点时执行此函数
 * @param inode        : 文件索引
 * @param file         : 文件
 * @return             :成功返回 0  
 ****************************************************************************************/
int misc_release(struct inode *inode, struct file *file)
{
    printk("hello misc_relaease bye bye \n ");
    return 0;
}
/****************************************************************************************
 * @brief misc_open : 用户空间打开设备节点时执行此函数
 * @param inode     : 文件索引
 * @param file      : 文件
 * @return          : 成功返回 0    
 ****************************************************************************************/
int misc_open(struct inode *inode, struct file *file)
{
    printk("hello misc_open\n ");
    return 0;
}
/****************************************************************************************
 * @brief misc_ioctl : 用户空间使用ioctl(int fd, unsigned long request, ...(void* arg))时
 *                      自动执行此函数,根据命令执行对应的操作
 * @param file       : 设备文件
 * @param cmd        : 用户空间ioctl接口命令request
 * @param value      : 用户空间的arg指针,依赖于接口命令request
 * @return           : 成功返回 0   
 ****************************************************************************************/
long misc_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{
    int val;
    switch (cmd)//根据命令进行对应的操作
    {
    case CMD_TEST2:
        printk("LED ON \n");
        printk("value is %ld\n", value);
        break;
    case CMD_TEST3:
        printk("LED OFF \n");
        printk("value is %ld\n", value);
        break;
    case CMD_TEST4:
        val = 12;
        if (copy_to_user((int *)value, &val, sizeof(val)) != 0)
        {
            printk("cpoy_to_usr error \n");
            return -1;
        }
        break;
    }
    return 0;
}
struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
    .unlocked_ioctl = misc_ioctl };
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops,
};
static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error \n");
    }
    printk("misc registe is succeed \n");

    return 0;
}
static void misc_exit(void)
{
    misc_deregister(&misc_dev);

    printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

将led.c编译为led.ko 文件,如下图所示:

应用程序代码ioctl.c代码如下:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define CMD_TEST0 _IO('L', 0)
#define CMD_TEST1 _IO('A', 1)

#define CMD_TEST2 _IOW('L', 2, int)
#define CMD_TEST3 _IOW('L', 3, int)
#define CMD_TEST4 _IOR('L', 4, int)

int main(int argc, char *argv[])
{
    int fd;
    int value;
    char buf[64] = {0};
    fd = open("/dev/hello_misc", O_RDWR);
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    while (1)
    {
        ioctl(fd, CMD_TEST4, &value);
        printf("value is %d \n", value);
        sleep(2);
    }
    return 0;
}

编译完成如我们进入共享目录,卸载原来加载的驱动模块,然后再加载新的驱动模块以后,再运行应用程序,如下图所示:

相关推荐
雯宝3 小时前
mnt_init 函数
linux
律宏阔9 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔9 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴9 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千9 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei379 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei379 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei379 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW9 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
GeW10 小时前
工业场景中的Linux与数据库选型:高端化转型的技术底座
linux