参考:[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实现的更多相关文章

  1. 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】

    转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...

  2. 嵌入式Linux驱动开发日记

    嵌入式Linux驱动开发日记 主机硬件环境 开发机:虚拟机Ubuntu12.04 内存: 1G 硬盘:80GB 目标板硬件环境 CPU: SP5V210 (开发板:QT210) SDRAM: 512M ...

  3. linux驱动初探之字符驱动

    关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...

  4. linux 驱动学习笔记01--Linux 内核的编译

    由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...

  5. Linux驱动学习步骤(转载)

    1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ls ...

  6. 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  7. Linux驱动开发学习的一些必要步骤

      1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ...

  8. Android系统移植与驱动开发——第六章——使用实例来理解Linux驱动开发及心得

    Linux驱动的工作方式就是交互.例如向Linux打印机驱动发送一个打印命令,可以直接使用C语言函数open打开设备文件,在使用C语言函数ioctl向该驱动的设备文件发送打印命令.编写Linux驱动最 ...

  9. 驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  10. Linux驱动之内核自带的S3C2440的LCD驱动分析

    先来看一下应用程序是怎么操作屏幕的:Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户 ...

随机推荐

  1. Scratch3自定义积木块之新增积木块

    在Scratch3.0的二次开发中,新功能的研发和扩展离不开积木块的添加,这篇主要讲解Scratch3.0中新增积木块部分 Scratch3.0中对于新增积木块有两种方式: 1. 初始化积木块方式   ...

  2. iOS的cer、p12格式证书解析监控

    之前博客写过直接解析ipa包获取mobileprovision文件来监控APP是否过期来,但APP的推送证书还没有做, 大家都知道,iOS的推送证书不会放到ipa包里,只能通过直接解析p12或cer. ...

  3. BGE M3-Embedding 模型介绍

    BGE M3-Embedding来自BAAI和中国科学技术大学,是BAAI开源的模型.相关论文在https://arxiv.org/abs/2402.03216,论文提出了一种新的embedding模 ...

  4. 树上点差分的经典应用 LuoguP3258松鼠的新家

    树上点差分的核心就是如何避免重复,即正确的运用差分数组 例如a,b点路径上点权值加1,则把a,b路径找到,并找到其LCA,此时可以把a到根,b到根这两条路径看出两条链,把每条链看出我们熟悉的 顺序差分 ...

  5. EasyNLP发布融合语言学和事实知识的中文预训练模型CKBERT

    简介: 本⽂简要介绍CKBERT的技术解读,以及如何在EasyNLP框架.HuggingFace Models和阿里云机器学习平台PAI上使⽤CKBERT模型. 导读 预训练语言模型在NLP的各个应用 ...

  6. 干货|一文读懂阿里云数据库Autoscaling是如何工作的

    简介: 阿里云数据库实现了其特有的Autosaling能力,该能力由数据库内核.管控及DAS(数据库自治服务)团队共同构建,内核及管控团队提供了数据库Autoscaling的基础能力,DAS则负责性能 ...

  7. Raft 共识算法2-领导者选举

    Raft 共识算法2-领导者选举 Raft算法中译版地址:https://object.redisant.com/doc/raft中译版-2023年4月23日.pdf 英原论文地址:https://r ...

  8. 第一章 Jenkins安装配置

    Jenkins官网 # 官网: https://www.jenkins.iohttps://www.jenkins.io/zh/ # docker安装: https://www.jenkins.io/ ...

  9. java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.base.BaseSelectProvider

    解决错误: java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.base.BaseSelectProvider 整合一遍通用mapp ...

  10. R3_Elasticsearch Index Setting

    索引的配置项按是否可以更改分为static属性与动态配置,所谓的静态配置即索引创建后不能修改.目录如下:生产环境中某索引结构(7.X后有变化) 索引静态配置 1.分片与压缩 index.number_ ...