今天带大家学习一下如何给一个新设备添加ioctl驱动接口!内容比较长,记得先点赞后观看哦!
ioctl 被称为输入和输出控制,用于与设备驱动程序对话。用户空间通过调用在大多数驱动程序类别中都可用。它的主要用途是处理内核默认没有系统调用的设备的某些特定操作。
ioctl
我们上面已经提到是内核态提供给用户态的一个接口函数,我们在用户态想要控制某个输入/输出设备只需要调用该函数即可,具体这个函数在内核态做了哪些处理我们无需关心。
ioctl 接口定义在 中,其中函数原型如下:
int ioctl(int fd, unsigned long request, ...);
fd 是要打开的文件描述符;request 是命令码;…是可变参数arg;其中命令码是应用程序来控制驱动程序完成对应操作的识别码,所以内核态就是通过该参数来确定我们调用 ioctl 到底是想干什么,至于怎么干那就不是你关心的事了,内核态的驱动程序就帮你完成了具体实现逻辑。
如果 ioctl 执行成功,它的返回值就是驱动程序中 ioctl 接口给的返回值,驱动程序可以通过返回值向用户程序传参。但驱动程序最好返回一个非负数,因为用户程序中的 ioctl 运行失败时一定会返回 -1 并设置全局变量 errorno。
ioctlioctl 的使用分为在用户空间内使用和在内核空间内使用,我们先来讲解一下用户空间内我们该如何调用 ioctl 接口。
使用 ioctl 涉及一些步骤:
ioctl 命令;ioctl 函数;ioctl 命令;ioctl 系统调用;ioctl 命令要实现新的 ioctl 命令,我们需要按照以下步骤来实现,至于什么是新的 ioctl 命令,你可以把它理解为你现在有一个自己设计的产品,你现在要为他写驱动和测试程序,那么就需要按照这个步骤来实现新的 ioctl 。
但是大多数情况下我们使用的产品大都是成熟的,其实我们不需要大家完全从0开始写,这里按照新设备来写,具体你需要写那部分可以自行决定。
1、定义ioctl命令
#define "ioctl name" __IOX("magic number","command number","argument type")
其中 IOX 可以是:
IO:没有参数的 ioctl
IOW:具有写入参数的 ioctl (copy_from_user)
IOR:具有读取参数的 ioctl (copy_to_user)
IOWR:具有写入和读取参数的 ioctl
其中的参数含义如下:
magic number:是一个唯一的Magic Number数字或字符,它将我们的 ioctl 调用集与其他 ioctl 调用区分开来。有时这里使用设备的主设备号。command number:命令编号是分配给 ioctl 的编号。这用于将命令彼此区分开来。argument type:最后是数据类型。定义完新 ioctl 后我们需要添加对应的头文件以使用该函数,具体实现如下:
#include
#define WR_VALUE _IOW('a','a',int32_t*)
#define RD_VALUE _IOR('a','b',int32_t*)
ioctl 函数下一步是将我们定义的ioctl调用实现到对应的驱动中。我们需要将 ioctl 函数添加到我们的驱动程序中,在下面找到函数的原型:
int ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
inode:是正在处理的文件的 inode 号。file:是指向应用程序传递的文件的文件指针。cmd: 是从用户空间调用的 ioctl 命令。arg:是从用户空间传递的参数在函数 ioctl 中,我们需要实现上面定义的所有命令(WR_VALUE, RD_VALUE)。我们需要在switch 上面定义的语句中使用相同的命令。
然后我们需要通知内核ioctl调用是在函数etx_ioctl中实现的。这是通过使fops 指针unlocked_ioctl来完成的etx_ioctl,如下所示:
/*
** This function will be called when we write IOCTL on the Device file
*/
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case WR_VALUE:
if( copy_from_user(&value ,(int32_t*) arg, sizeof(value)) )
{
pr_err("Data Write : Err!\n");
}
pr_info("Value = %d\n", value);
break;
case RD_VALUE:
if( copy_to_user((int32_t*) arg, &value, sizeof(value)) )
{
pr_err("Data Read : Err!\n");
}
break;
default:
pr_info("Default\n");
break;
}
return 0;
}
/*
** File operation sturcture
*/
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.unlocked_ioctl = etx_ioctl,
.release = etx_release,
};
现在我们就可以从用户应用程序调用 ioctl 命令。
ioctl 系统调用包括头文件 ,现在我们需要从用户应用程序调用新的 ioctl 命令。
long ioctl( "file descriptor","ioctl command","Arguments");
其中参数含义如下:
file descriptor:这是需要执行ioctl 命令的打开文件,通常是设备文件。ioctl command:实现所需功能的 ioctl 命令arguments:需要将参数传递给 ioctl 命令。举个例子:
ioctl ( fd, WR_VALUE, ( int32_t * ) &number ) ;
ioctl ( fd, RD_VALUE, ( int32_t * ) &value ) ;
现在我们将看到完整的驱动程序和应用程序。
ioctl – 设备驱动程序源代码在这个例子中,我们只实现了 ioctl 。在这个驱动程序中,我定义了一个变量 ( int32_t value)。使用 ioctl 命令我们可以读取或更改变量。所以其他函数,如打开、关闭、读取和写入,我们只是留空,只需通过下面的代码:
driver.c
/***************************************************************************//**
* \file driver.c
*
* \details Simple Linux device driver (IOCTL)
*
* \author EmbeTronicX
*
* \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include //kmalloc()
#include //copy_to/from_user()
#include
#define WR_VALUE _IOW('a','a',int32_t*)
#define RD_VALUE _IOR('a','b',int32_t*)
int32_t value = 0;
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
/*
** Function Prototypes
*/
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off);
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
/*
** File operation sturcture
*/
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.unlocked_ioctl = etx_ioctl,
.release = etx_release,
};
/*
** This function will be called when we open the Device file
*/
static int etx_open(struct inode *inode, struct file *file)
{
pr_info("Device File Opened...!!!\n");
return 0;
}
/*
** This function will be called when we close the Device file
*/
static int etx_release(struct inode *inode, struct file *file)
{
pr_info("Device File Closed...!!!\n");
return 0;
}
/*
** This function will be called when we read the Device file
*/
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
pr_info("Read Function\n");
return 0;
}
/*
** This function will be called when we write the Device file
*/
static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
pr_info("Write function\n");
return len;
}
/*
** This function will be called when we write IOCTL on the Device file
*/
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case WR_VALUE:
if( copy_from_user(&value ,(int32_t*) arg, sizeof(value)) )
{
pr_err("Data Write : Err!\n");
}
pr_info("Value = %d\n", value);
break;
case RD_VALUE:
if( copy_to_user((int32_t*) arg, &value, sizeof(value)) )
{
pr_err("Data Read : Err!\n");
}
break;
default:
pr_info("Default\n");
break;
}
return 0;
}
/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
/*Allocating Major number*/
if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
pr_err("Cannot allocate major number\n");
return -1;
}
pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
/*Creating cdev structure*/
cdev_init(&etx_cdev,&fops);
/*Adding character device to the system*/
if((cdev_add(&etx_cdev,dev,1)) < 0){
pr_err("Cannot add the device to the system\n");
goto r_class;
}
/*Creating struct class*/
if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
pr_err("Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
pr_err("Cannot create the Device 1\n");
goto r_device;
}
pr_info("Device Driver Insert...Done!!!\n");
return 0;
r_device:
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev,1);
return -1;
}
/*
** Module exit function
*/
static void __exit etx_driver_exit(void)
{
device_destroy(dev_class,dev);
class_destroy(dev_class);
cdev_del(&etx_cdev);
unregister_chrdev_region(dev, 1);
pr_info("Device Driver Remove...Done!!!\n");
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX " );
MODULE_DESCRIPTION("Simple Linux device driver (IOCTL)");
MODULE_VERSION("1.5");
Makefile
obj-m += driver.o
KDIR = /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(shell pwd) modules
clean:
make -C $(KDIR) M=$(shell pwd) clean
test_app.c
/***************************************************************************//**
* \file test_app.c
*
* \details Userspace application to test the Device driver
*
* \author EmbeTronicX
*
* \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#define WR_VALUE _IOW('a','a',int32_t*)
#define RD_VALUE _IOR('a','b',int32_t*)
int main()
{
int fd;
int32_t value, number;
printf("*********************************\n");
printf("*******WWW.EmbeTronicX.com*******\n");
printf("\nOpening Driver\n");
fd = open("/dev/etx_device", O_RDWR);
if(fd < 0) {
printf("Cannot open device file...\n");
return 0;
}
printf("Enter the Value to send\n");
scanf("%d",&number);
printf("Writing Value to Driver\n");
ioctl(fd, WR_VALUE, (int32_t*) &number);
printf("Reading Value from Driver\n");
ioctl(fd, RD_VALUE, (int32_t*) &value);
printf("Value is %d\n", value);
printf("Closing Driver\n");
close(fd);
}
编译
gcc -o test_app test_app.c
到目前为止,我们有driver.ko和test_app,现在我们将测试输出。
使用加载驱动程序 sudo insmod driver.ko
运行应用程序 sudo ./test_app
23456
Writing Value to Driver
Reading Value from Driver
Value is 23456
Closing Driver
我们可以使用dmesg检查驱动的加载情况。
Device File Opened...!!!
Value = 23456
Device File Closed...!!!
可以看到我们的值 23456 已传递给内核并已更新。
这是一个在 Linux 设备驱动程序中使用 ioctl 的简单示例。如果要发送多个参数,请将这些变量放入结构中,并传递结构的地址。
今天的文章就分享到这里,如果你对Linux驱动开发感兴趣的话可以关注一下我的CSDN和下方的微信公众号。