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这个设备来供用户 ...
随机推荐
- ABP -Vnext框架一步一步入门落地教程——使用ABP -Vnext创建一个WEBAPI接口(二)
开发主题:何谓开发应用服务端 在官方开发教程这一段的内容叫做开发应用服务端,作为现在前后端分离的开发模式来说,一个应用就分为前端页面框架和后端API,页面框架调用WEBAPI实现业务就完事了.所以咱们 ...
- 6980. 【2021.02.03冬令营模拟】你的世界(world) Another Solution
Problem Description Input 从文件 world.in 中读入数据. Output 输出到文件 world.out 中. 输出共 T 行,第 i 行表示第 i 组测试数据的答案, ...
- 【Oracle】使用PL/SQL快速查询出1-9数字
[Oracle]使用PL/SQL快速查询出1-9数字 简单来说,直接Recursive WITH Clauses 在Oracle 里面就直接使用WITH result(参数)即可 WITH resul ...
- 网易游戏基于 Flink 的流式 ETL 建设
简介: 网易游戏流式 ETL 建设实践及调优经验分享- 网易游戏资深开发工程师林小铂为大家带来网易游戏基于 Flink 的流式 ETL 建设的介绍.内容包括: 专用 ETL EntryX 通用 ETL ...
- [FE] Quasar BEX 所有位置类型 types
科普:[FE] Quasar BEX 预览版指南 New Tab Quasar BEX 的默认类型是 New Tab,在新 tab 栏里打开内容. Dev Tools 也就是在开发者栏里面的内容. O ...
- [FAQ] Jetbrains 官网不能访问,获取 Goland 的下载地址
2020.02 安装包下载 Link:https://www.cnblogs.com/farwish/p/14186441.html
- Fixing Missing Windows App Runtime Environment Prompt for Unpackaged WinUI 3 Applications
This article will tell you how to fix the prompt for a missing Windows App Runtime environment when ...
- nginx部署使用history模式的vue项目详细配置【可多级目录】
介绍 本文是篇详细的介绍vue项目在history模式下发布时build,项目如何配置,nginx如何配置,才能正常的使用历史模式.或者在二级目录下,多级路径下也能正常使用历史模式. 本文的例子中假设 ...
- 累计预扣法个税,怎么算?(附excel)
累计预扣法个税计算 依法纳税是每个公民的义务,但看着每个月递增的个税,你可能会发出疑问,这到底是怎么算的?这就要引出2019年1月1日实施新实施的个税法,累计预扣法.即自2019年1月1日起,居民个人 ...
- ITIL现有版本之间的区别
时代在变化,运维管理理论也在不断演进升级,不断学习是运维人的良好品质:虽然人有的时候会懈怠,理论学习的道路也较单调乏味,但终究还是要跟上时代的步调才能适应新的变化