Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

2024-03-07 17:48

本文主要是介绍Linux嵌入式驱动开发13——ioctl接口(gpio控制使用),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 全系列传送门
  • 引言
  • 什么是unlocked_ioctl接口?
  • unlocked_ioctl和read/write函数有什么相同和不同
  • unlocked_ioctl接口命令规则
  • 命令的合成宏与分解宏
    • 合成宏
    • 分解宏
    • 测试程序
  • 使用_IO
    • module_leds.c
    • Makefile
    • app.c
    • 结果
  • 使用_IOW
  • 使用_IOR
    • 实验结果
  • 完整代码
    • module_leds.c
    • app.c

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

引言

我们从平台总线模型,然后到pinctrl和gpio子系统,会发现步骤逐渐的规范,代码也逐渐的简单,也越来越能体会到linux屏蔽底层硬件的优势。

在之前的代码中,在write函数中往内核中写入数据。
在这里插入图片描述
并且在应用层传入内核层的过程,需要执行copy函数,对于我们这里只需要传入0或者1的少量数据来说,有些繁琐。

什么是unlocked_ioctl接口?

unlocked_ioctl就是ioctl接口,但是功能和对应的系统调用均没有发生变化

unlocked_ioctl和read/write函数有什么相同和不同

之前我们使用read/write函数完成写数据或者读数据的操作,ioctl函数也可以往内核中写入命令。

不同点是read/write函数是两个单独的函数,完成单一的读或者写功能,我们的ioctl函数,既可以读,也可以写。

不过read/write函数在读写大数据时候效率比较高。

所以,对于gpio控制led灯或者蜂鸣器等操作时,我们不需要大量的数据读写,可以使用ioctl函数,来简化,而read/write函数专职于大量数据的传输。

unlocked_ioctl接口命令规则

unlocked_ioctl总共32位

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

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

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

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

第四个分区 30-31 代表读写的方向
00:表示用户程序和驱动数据没有数据传递
10:表示用户程序从驱动里面读取数据
01:表示用户程序向驱动里面写入数据

11:先写数据到驱动,然后再从驱动把数据读出来(不常用)

命令的合成宏与分解宏

合成宏

在这里插入图片描述

分解宏

在这里插入图片描述

测试程序

#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)                       // 'L'是幻数 0是编号
#define CMD_TEST1 _IO('L', 1)
#define CMD_TEST2 _IOW('L', 2, int)                 // 第三个参数size,不是数据大小,而是数据类型,这里是四个字节的话,就是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));return 0;
}

我们先来测试一下代码的功能,这里使用_IOC_DIR分解命令的方向,CMD_TEST0是定义命令编号,没有数据操作,所以_IOC_DIR分解后30-31的数据应该是00

CMD_TEST3是_IOR(‘L’, 3, int),是读取,所以分解后应该读到是读对应的10,对应十进制也就是2
在这里插入图片描述
接下来继续测试_IOC_TYPE函数

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

执行后可以看到返回了8-15位的幻数
在这里插入图片描述

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

在这里插入图片描述

使用_IO

模拟一个闪灯的操作

module_leds.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>#define CMD_TEST0 _IO('L', 0)                       // 'L'是幻数 0是编号
#define CMD_TEST1 _IO('L', 1) int misc_open (struct inode *inode, struct file *file){printk("hello misc_open!!!\n");return 0;
}int misc_release(struct inode *inode, struct file *file){printk("bye bye misc_release!!!\n");return 0;}ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){char kbuf[64] = "copy to user!!!\n";if( copy_to_user(ubuf, kbuf, size) != 0 ){printk("copy_to_user error!!!\n");return -1;}printk("hello misc_read!!!\n");return 0;
}ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){char kbuf[64] = "copy from user!!!\n";printk("hello misc_write!!!\n");if( copy_from_user(kbuf, ubuf, size) != 0 ){printk("copy_from_user error!!!\n");return -1;}printk("buf is:%s\n", kbuf);return 0;
}long misc_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{switch (cmd){case CMD_TEST0:printk("LED_ON!!!\n");case CMD_TEST1:printk("LED_OFF!!!\n");break;default: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_register failed!!!\n");return -1;}printk("misc_register succeed!!!\n");           // 在内核中无法使用c语言库,所以不用printfreturn 0;
}static void misc_exit(void)
{misc_deregister(&misc_dev);printk("misc exit!!!\n");
}module_init(misc_init);
module_exit(misc_exit);MODULE_LICENSE("GPL");              //声明模块拥有开源许可

Makefile

# 开发板Linux内核的实际路径 
# KDIR变量
KDIR:=/work/linux-4.1.15#  获取当前目录
PWD:=$(shell pwd)# obj-m表示将 chrdevbase.c这个文件 编译为 chrdevbase.ko模块。
obj-m += module_leds.o# 编译成模块
all:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean

app.c

#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)                       // 'L'是幻数 0是编号
#define CMD_TEST1 _IO('L', 1) int main(int argc, char *argv[])
{int fd;char buff[64] = {0};fd = open("/dev/hello_misc", O_RDWR);       // 打开节点时候触发open函数if(fd < 0){perror("open error\n");                 // perror在应用中打印return fd;}while(1){ioctl(fd, CMD_TEST0);                   // 触发驱动中的.unlocked_ioctlsleep(2);ioctl(fd, CMD_TEST1);sleep(2);}return 0;
}

结果

在测试结果中可以看到每次ioctl都会进入到驱动中的misc_ioctl中进行判断,然后打印出来预期的结果
在这里插入图片描述

使用_IOW

这里只修改了两个地方的代码
module_leds.c

long misc_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{switch (cmd){case CMD_TEST2:printk("LED_ON!!!\n");printk("value is %d\n", value);break;case CMD_TEST3:printk("LED_OFF!!!\n");printk("value is %d\n", value);break;default:break;}return 0;
}

app.c

    while(1){ioctl(fd, CMD_TEST2, 0);                   // 触发驱动中的.unlocked_ioctlsleep(2);                               // 每隔两秒钟ioctl(fd, CMD_TEST3, 1);sleep(2);}

这里的一一对应的关系如图所示
在这里插入图片描述
这样,我们的实验结果就是,可以正常的写入数据
在这里插入图片描述

使用_IOR

有些时候我们需要读取按键的值,或者引脚的状态,使用_IOR就会很方便

在应用函数里,设置如下,传入value的地址到ioctl函数

    while(1){ioctl(fd, CMD_TEST4, &value);                   // 触发驱动中的.unlocked_ioctlprintf("app value is %d\n", value);sleep(2);                               // 每隔两秒钟}

在驱动函数中,依然要使用copy_to_user函数,复制数据从底层到应用层中,所以,在这里第一个参数就是应用层的value的地址,因为是int型指针,所以要强制转换一下,然后第二个参数就是底层的val值,第三个就是底层val的长度大小

    case CMD_TEST4:val = 12;if( copy_to_user((int *)value, &val, sizeof(val)) != 0 ){printk("copy_to_user error!!!\n");return -1;}break;

实验结果

在这里插入图片描述
我们程序中的val可以替换为底层io口的状态。

完整代码

module_leds.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>#define CMD_TEST0 _IO('L', 0)                       // 'L'是幻数 0是编号
#define CMD_TEST1 _IO('L', 1) #define CMD_TEST2 _IOW('L', 2, int) 
#define CMD_TEST3 _IOW('L', 3, int) #define CMD_TEST4 _IOR('L', 4, int) int misc_open (struct inode *inode, struct file *file){printk("hello misc_open!!!\n");return 0;
}int misc_release(struct inode *inode, struct file *file){printk("bye bye misc_release!!!\n");return 0;}ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){char kbuf[64] = "copy to user!!!\n";if( copy_to_user(ubuf, kbuf, size) != 0 ){printk("copy_to_user error!!!\n");return -1;}printk("hello misc_read!!!\n");return 0;
}ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){char kbuf[64] = "copy from user!!!\n";printk("hello misc_write!!!\n");if( copy_from_user(kbuf, ubuf, size) != 0 ){printk("copy_from_user error!!!\n");return -1;}printk("buf is:%s\n", kbuf);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 %d\n", value);break;case CMD_TEST3:printk("LED_OFF!!!\n");printk("value is %d\n", value);break;case CMD_TEST4:val = 12;if( copy_to_user((int *)value, &val, sizeof(val)) != 0 ){printk("copy_to_user error!!!\n");return -1;}break;default: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_register failed!!!\n");return -1;}printk("misc_register succeed!!!\n");           // 在内核中无法使用c语言库,所以不用printfreturn 0;
}static void misc_exit(void)
{misc_deregister(&misc_dev);printk("misc exit!!!\n");
}module_init(misc_init);
module_exit(misc_exit);MODULE_LICENSE("GPL");              //声明模块拥有开源许可

app.c

#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)                       // 'L'是幻数 0是编号
#define CMD_TEST1 _IO('L', 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;fd = open("/dev/hello_misc", O_RDWR);       // 打开节点时候触发open函数if(fd < 0){perror("open error\n");                 // perror在应用中打印return fd;}while(1){ioctl(fd, CMD_TEST4, &value);                   // 触发驱动中的.unlocked_ioctlprintf("app value is %d\n", value);sleep(2);                               // 每隔两秒钟}return 0;
}

这篇关于Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/784330

相关文章

Spring Boot整合Redis注解实现增删改查功能(Redis注解使用)

《SpringBoot整合Redis注解实现增删改查功能(Redis注解使用)》文章介绍了如何使用SpringBoot整合Redis注解实现增删改查功能,包括配置、实体类、Repository、Se... 目录配置Redis连接定义实体类创建Repository接口增删改查操作示例插入数据查询数据删除数据更

使用python生成固定格式序号的方法详解

《使用python生成固定格式序号的方法详解》这篇文章主要为大家详细介绍了如何使用python生成固定格式序号,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... 目录生成结果验证完整生成代码扩展说明1. 保存到文本文件2. 转换为jsON格式3. 处理特殊序号格式(如带圈数字)4

Java使用Swing生成一个最大公约数计算器

《Java使用Swing生成一个最大公约数计算器》这篇文章主要为大家详细介绍了Java使用Swing生成一个最大公约数计算器的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下... 目录第一步:利用欧几里得算法计算最大公约数欧几里得算法的证明情形 1:b=0情形 2:b>0完成相关代码第二步:加

linux ssh如何实现增加访问端口

《linuxssh如何实现增加访问端口》Linux中SSH默认使用22端口,为了增强安全性或满足特定需求,可以通过修改SSH配置来增加或更改SSH访问端口,具体步骤包括修改SSH配置文件、增加或修改... 目录1. 修改 SSH 配置文件2. 增加或修改端口3. 保存并退出编辑器4. 更新防火墙规则使用uf

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三