Linux驱动--IOCTL实现
参考:[Linux]实现设备驱动的ioctl函数_哔哩哔哩_bilibili、《Linux设备驱动程序(中文第三版).pdf》
1 用户空间ioctl
用户空间的ioctl函数原型,参数是可变参数,实际为单个可选的参数:
#include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...);
2 内核驱动ioctl
字符设备的文件操作集中的ioctl实现,函数原型:
/* 参数filp:文件描述符指针
* 参数cmd:用户空间传递的命令
* 参数arg:对应命令的参数
* 返回值:通常返回-EINVAL,表示一个无效的ioctl命令。
*/
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
KO是64位,应用程序是32位的时候,使用的是compat_ioctl函数。
参数cmd是32位整型,不是简单的数字,其在系统中应该是唯一的,其构成如下:
include/uapi/asm-generic/ioctl.h
---------------------------------------
| direction | size | type | number |
---------------------------------------
| 2bits | 14bits | 8bits | 8bits |
---------------------------------------
direction:表示方向,如果命令涉及数据传输,可能的值为_IOC_NONE(没有数据传输)、_IOC_WRITE、_IOC_READ、_IOC_WRITE|_IOC_READ,数据传输从应用程序的角度看,_IOC_READ表示从设备读,也就是设备写到用户空间。
# define _IOC_NONE 0U
# define _IOC_WRITE 1U
# define _IOC_READ 2U
size:参数数据大小,位宽是依赖体系,通常是13或14位,可通过宏_IOC_SIZEBITS找到值。
# define _IOC_SIZEBITS 14
type:幻数,只是选择了一个数(参考Documentation/ioctl/ioctl-number.txt)并且在驱动中使用它,位宽为8位。
#define _IOC_TYPEBITS 8
number:序号,位宽8位。
有些cmd已经被使用,需要先检查:Documentation/ioctl/ioctl-number.txt
2.1 命令号创建
可通过内核定义的宏来创建命令号:
/* used to create numbers */
/* 给没有参数的命令 */
#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)))
另外,内核还提供了解码命令号的宏:
/* used to decode ioctl numbers.. */
#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)
3 实验
3.1 驱动和APP
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/device.h>
#include "test_ioctl.h"
#define GLOBALMEM_BUFFER_SIZE 0x1000
#define GLOBALMEM_DEVICE_NUM 2
#define GLOBALMEM_NAME "globalmem"
struct globalmem_dev {
struct cdev cdev;
dev_t devid; /* 设备号,高12位是主设备号,低20位是次设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
char buffer[GLOBALMEM_BUFFER_SIZE]; /* 缓存 */
int cur_chars; /* 缓存中当前字符个数 */
};
struct globalmem_dev *globalmem_devp;
int globalmem_open(struct inode *inode, struct file *filp)
{
struct globalmem_dev *dev;
// printk(KERN_INFO "%s open \n",current->comm);
dev = container_of(inode->i_cdev, struct globalmem_dev, cdev); //获取设备结构体的地址
filp->private_data = dev; //将设备结构地址放到文件描述符结构的私有数据中
return 0;
}
ssize_t globalmem_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
int ret = 0;
unsigned long p = *f_pos;
struct globalmem_dev *dev = filp->private_data;
if (p >= GLOBALMEM_BUFFER_SIZE) {
return 0;
}
if (count > GLOBALMEM_BUFFER_SIZE - p) {
count = GLOBALMEM_BUFFER_SIZE - p;
}
/* 内核空间到用户空间缓存区的复制 */
if (copy_to_user(buf, dev->buffer + p, count)) {
return -EFAULT;
}
*f_pos += count;
ret = count;
if (dev->cur_chars - count <= 0) {
dev->cur_chars = 0;
} else {
dev->cur_chars -= count;
}
printk(KERN_INFO "read %lu bytes(s) from %lu\n", (unsigned long)count, p);
return ret;
}
ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
int ret = 0;
unsigned long p = *f_pos;
struct globalmem_dev *dev = filp->private_data;
if (p >= GLOBALMEM_BUFFER_SIZE) {
return 0;
}
if (count > GLOBALMEM_BUFFER_SIZE - p) {
count = GLOBALMEM_BUFFER_SIZE - p;
}
/* 用户空间缓存区到内核空间缓存区的复制 */
if (copy_from_user(dev->buffer + p, buf, count)) {
return -EFAULT;
}
*f_pos += count;
ret = count;
dev->cur_chars += count;
printk(KERN_INFO "written %lu bytes(s) from %lu\n", (unsigned long)count, p);
return ret;
}
long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long retval = 0;
int tmp = 0;
struct globalmem_dev *dev = filp->private_data;
/* 检查幻数(返回值POSIX标准规定,也用-EINVAL) */
if (_IOC_TYPE(cmd) != HC_IOC_MAGIC) {
return -ENOTTY;
}
/* 检查命令编号 */
if (_IOC_NR(cmd) > HC_IOC_MAXNR) {
return -ENOTTY;
}
printk(KERN_INFO "cmd(0x%x) decode, direction: %u, type: %u, number: %u, size: %u\r\n",
cmd, _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd));
switch(cmd) {
case HC_IOC_RESET:
printk(KERN_INFO "ioctl reset\n");
memset(dev->buffer, 0, GLOBALMEM_BUFFER_SIZE);
dev->cur_chars = 0;
break;
case HC_IOCP_GET_LENS:
printk(KERN_INFO "ioctl get lens through pointer\n");
retval = put_user(dev->cur_chars, (int __user *)arg);
break;
case HC_IOCV_GET_LENS:
printk(KERN_INFO "ioctl get lens through value\n");
return dev->cur_chars;
break;
case HC_IOCP_SET_LENS:
printk(KERN_INFO "ioctl set lens through pointer");
if (! capable (CAP_SYS_ADMIN)) {
return -EPERM;
}
retval = get_user(tmp, (int __user *)arg);
if(dev->cur_chars > tmp) {
dev->cur_chars = tmp;
}
printk(KERN_INFO " %d\n", dev->cur_chars);
break;
case HC_IOCV_SET_LENS:
printk(KERN_INFO "ioctl set lens through value");
if (!capable (CAP_SYS_ADMIN)) {
return -EPERM;
}
dev->cur_chars = min(dev->cur_chars, (int)arg);
printk(KERN_INFO " %d\n", dev->cur_chars);
break;
}
return retval;
}
int globalmem_release(struct inode *inode, struct file *filp)
{
// printk(KERN_INFO "%s release\n",current->comm);
return 0;
}
/* 字符设备的操作函数 */
struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.open = globalmem_open,
.read = globalmem_read,
.write = globalmem_write,
.release = globalmem_release,
.unlocked_ioctl = globalmem_ioctl,
};
static int __init globalmem_init(void)
{
int ret = 0;
int i = 0;
dev_t devid = 0;
struct class *class = NULL;
printk(KERN_INFO "---BEGIN HELLO LINUX MODULE---\n");
globalmem_devp = kzalloc(sizeof(struct globalmem_dev) * GLOBALMEM_DEVICE_NUM, GFP_KERNEL);
if (!globalmem_devp) {
printk(KERN_WARNING "alloc mem failed");
return -ENOMEM;
}
memset(globalmem_devp, 0x0, sizeof(struct globalmem_dev) * GLOBALMEM_DEVICE_NUM);
/* 动态分配主设备号 */
ret = alloc_chrdev_region(&devid, 0, GLOBALMEM_DEVICE_NUM, GLOBALMEM_NAME);;
if (ret < 0) {
printk(KERN_WARNING "hello: can't alloc major!\n");
goto fail;
}
/* 初始化cdev */
for (i = 0; i < GLOBALMEM_DEVICE_NUM; i++) {
globalmem_devp[i].major = MAJOR(devid);
globalmem_devp[i].minor = i;
globalmem_devp[i].devid = MKDEV(globalmem_devp[i].major, globalmem_devp[i].minor);
cdev_init(&globalmem_devp[i].cdev, &globalmem_fops);
globalmem_devp[i].cdev.owner = THIS_MODULE;
ret = cdev_add(&globalmem_devp[i].cdev, globalmem_devp[i].devid, 1);
if(ret) {
printk(KERN_WARNING "fail add globalmem char device: %d\r\n", i);
}
}
class = class_create(THIS_MODULE, "hc_dev");
if (!class) {
printk(KERN_WARNING "fail create class\r\n");
ret = PTR_ERR(class);
goto failure_class;
}
for(i = 0; i < GLOBALMEM_DEVICE_NUM; i++) {
globalmem_devp[i].class = class;
globalmem_devp[i].device = device_create(class, NULL, globalmem_devp[i].devid, NULL, "globalmem_dev%d", i);
if (IS_ERR(globalmem_devp[i].device)) {
ret = PTR_ERR(globalmem_devp[i].device);
goto fail_device_create;
}
}
printk(KERN_INFO "---END HELLO LINUX MODULE---\n");
return 0;
failure_class:
unregister_chrdev_region(devid, GLOBALMEM_DEVICE_NUM);
fail_device_create:
class_destroy(class);
unregister_chrdev_region(devid, GLOBALMEM_DEVICE_NUM);
fail:
if (globalmem_devp) {
kfree(globalmem_devp);
}
return ret;
}
static void __exit globalmem_exit(void)
{
int i;
for (i = 0; i < GLOBALMEM_DEVICE_NUM; i++) {
device_destroy(globalmem_devp[i].class, globalmem_devp[i].devid);
}
class_destroy(globalmem_devp[0].class);
for (i = 0; i < GLOBALMEM_DEVICE_NUM; i++) {
cdev_del(&globalmem_devp[i].cdev);
}
/* 释放设备号 */
unregister_chrdev_region(globalmem_devp[0].devid, GLOBALMEM_DEVICE_NUM);
kfree(globalmem_devp);
printk(KERN_INFO "GOODBYE LINUX\n");
}
module_init(globalmem_init);
module_exit(globalmem_exit);
//描述性定义
MODULE_LICENSE("GPL");
MODULE_AUTHOR("KGZ");
MODULE_VERSION("V1.0");
/*****补充
1、compat_ioctl:支持64bit的driver必须要实现的ioctl,当有32bit的userspace application call 64bit kernel的IOCTL的时候,这个callback会被调用到。如果没有实现compat_ioctl,那么32位的用户程序在64位的kernel上执行ioctl时会返回错误:Not a typewriter
2、如果是64位的用户程序运行在64位的kernel上,调用的是unlocked_ioctl,如果是32位的APP运行在32位的kernel上,调用的也是unlocked_ioctl
*****/
#ifndef TEST_IOCTL_H
#define TEST_IOCTL_H
#define HC_IOC_MAGIC 0x81 /* Documentation/userspace-api/ioctl/ioctl-number.rst */
/* 复位命令,直接清除值 */
#define HC_IOC_RESET _IO(HC_IOC_MAGIC, 0)
/* 从驱动中读取数据,通过指针返回 */
#define HC_IOCP_GET_LENS _IOR(HC_IOC_MAGIC, 1, int)
/* 从驱动中读取数据,通过返回值返回 */
#define HC_IOCV_GET_LENS _IO(HC_IOC_MAGIC, 2)
/* 写数据到驱动,通过指针设置 */
#define HC_IOCP_SET_LENS _IOW(HC_IOC_MAGIC, 3, int)
/* 写数据到驱动,通过值设置 */
#define HC_IOCV_SET_LENS _IO(HC_IOC_MAGIC, 4)
/*正常情况不会混用,要不都按值传递,要不都用指针,这里只是演示*/
#define HC_IOC_MAXNR 4
#endif /* TEST_IOCTL_H */
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h>
#include<errno.h>
#include"test_ioctl.h"
static int check_input_para(int argc, int num)
{
if (argc != num) {
printf("Error Usage!\r\n");
return -1;
}
return 0;
}
int main(int argc ,char* argv[])
{
int n,retval=0;
int fd;
if (argc < 2) {
printf("Error Usage!\r\n");
return -1;
}
fd = open("/dev/globalmem_dev0",O_RDWR);
switch(argv[1][0]) {
case '0':
ioctl(fd, HC_IOC_RESET);
printf("reset globalmem\n");
break;
case '1':
ioctl(fd, HC_IOCP_GET_LENS, &n);
printf("app get lens pointer: %d\n", n);
break;
case '2':
n = ioctl(fd, HC_IOCV_GET_LENS);
printf("app get lens value: %d\n", n);
break;
case '3':
if (check_input_para(argc, 3))
goto error_exit;
n=argv[2][0] - '0';
retval = ioctl(fd,HC_IOCP_SET_LENS,&n);
printf("set lens value, %d %s\n",n,strerror(errno));
break;
case '4':
if (check_input_para(argc, 3))
goto error_exit;
n=argv[2][0] - '0';
retval = ioctl(fd,HC_IOCV_SET_LENS,n);
printf("set lens value, %d %s\n",n,strerror(errno));
break;
}
error_exit:
close(fd);
return 0;
}
3.2 测试
$ insmod test_ioctl_drv.ko
[75555.254838] ---BEGIN HELLO LINUX MODULE---
[75555.255797] ---END HELLO LINUX MODULE---
$ echo 12345678 > /dev/hc_dev0
$ cat /dev/hc_dev0
12345678
$ ./test_ioctl_app 0
reset hc
cmd(0x8100) decode, direction: 0, type: 129, number: 0, size: 0
ioctl reset
$ cat /dev/hc_dev0
$ echo 12345678 > /dev/hc_dev0
$ ./test_ioctl_app 1
get lens pointer, 9
$ ./test_ioctl_app 2
get lens value, 9
$ ./test_ioctl_app 3 5
[ 527.935609] cmd(0x40048103) decode, direction: 1, type: 129, number: 3, size: 4
[ 527.935611] ioctl set lens through pointer
set lens value, 5 Success
$ cat /dev/hc_dev0
12345
$ ./test_ioctl_app 4 4
[ 570.783527] cmd(0x8104) decode, direction: 0, type: 129, number: 4, size: 0
[ 570.783529] ioctl set lens through value
set lens value, 4 Success
$ cat /dev/hc_dev0
1234
Linux驱动--IOCTL实现的更多相关文章
- 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】
转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...
- 嵌入式Linux驱动开发日记
嵌入式Linux驱动开发日记 主机硬件环境 开发机:虚拟机Ubuntu12.04 内存: 1G 硬盘:80GB 目标板硬件环境 CPU: SP5V210 (开发板:QT210) SDRAM: 512M ...
- linux驱动初探之字符驱动
关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...
- linux 驱动学习笔记01--Linux 内核的编译
由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...
- Linux驱动学习步骤(转载)
1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ls ...
- 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)
这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...
- Linux驱动开发学习的一些必要步骤
1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ...
- Android系统移植与驱动开发——第六章——使用实例来理解Linux驱动开发及心得
Linux驱动的工作方式就是交互.例如向Linux打印机驱动发送一个打印命令,可以直接使用C语言函数open打开设备文件,在使用C语言函数ioctl向该驱动的设备文件发送打印命令.编写Linux驱动最 ...
- 驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED
这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...
- Linux驱动之内核自带的S3C2440的LCD驱动分析
先来看一下应用程序是怎么操作屏幕的:Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户 ...
随机推荐
- Scratch3自定义积木块之新增积木块
在Scratch3.0的二次开发中,新功能的研发和扩展离不开积木块的添加,这篇主要讲解Scratch3.0中新增积木块部分 Scratch3.0中对于新增积木块有两种方式: 1. 初始化积木块方式 ...
- iOS的cer、p12格式证书解析监控
之前博客写过直接解析ipa包获取mobileprovision文件来监控APP是否过期来,但APP的推送证书还没有做, 大家都知道,iOS的推送证书不会放到ipa包里,只能通过直接解析p12或cer. ...
- BGE M3-Embedding 模型介绍
BGE M3-Embedding来自BAAI和中国科学技术大学,是BAAI开源的模型.相关论文在https://arxiv.org/abs/2402.03216,论文提出了一种新的embedding模 ...
- 树上点差分的经典应用 LuoguP3258松鼠的新家
树上点差分的核心就是如何避免重复,即正确的运用差分数组 例如a,b点路径上点权值加1,则把a,b路径找到,并找到其LCA,此时可以把a到根,b到根这两条路径看出两条链,把每条链看出我们熟悉的 顺序差分 ...
- EasyNLP发布融合语言学和事实知识的中文预训练模型CKBERT
简介: 本⽂简要介绍CKBERT的技术解读,以及如何在EasyNLP框架.HuggingFace Models和阿里云机器学习平台PAI上使⽤CKBERT模型. 导读 预训练语言模型在NLP的各个应用 ...
- 干货|一文读懂阿里云数据库Autoscaling是如何工作的
简介: 阿里云数据库实现了其特有的Autosaling能力,该能力由数据库内核.管控及DAS(数据库自治服务)团队共同构建,内核及管控团队提供了数据库Autoscaling的基础能力,DAS则负责性能 ...
- Raft 共识算法2-领导者选举
Raft 共识算法2-领导者选举 Raft算法中译版地址:https://object.redisant.com/doc/raft中译版-2023年4月23日.pdf 英原论文地址:https://r ...
- 第一章 Jenkins安装配置
Jenkins官网 # 官网: https://www.jenkins.iohttps://www.jenkins.io/zh/ # docker安装: https://www.jenkins.io/ ...
- java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.base.BaseSelectProvider
解决错误: java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.base.BaseSelectProvider 整合一遍通用mapp ...
- R3_Elasticsearch Index Setting
索引的配置项按是否可以更改分为static属性与动态配置,所谓的静态配置即索引创建后不能修改.目录如下:生产环境中某索引结构(7.X后有变化) 索引静态配置 1.分片与压缩 index.number_ ...