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的时候,使用的是逻辑判断,kbuf[0]的数据是从应用层传递过来的我们要使用到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;
}
编译完成如我们进入共享目录,卸载原来加载的驱动模块,然后再加载新的驱动模块以后,再运行应用程序,如下图所示: