linux驱动学习(十五)之ioctl
一般而言字符设备驱动不可能只会调用读写操作(read/write),因为字符设备还需要进行其他参数的配置(如摄像头驱动 设置摄像头的画面参数、获取摄像头的能力......),像这样的操作都不会使用读写函数来实现,一般内核都会交给ioctl函数来实现,像ioctl函数的特点就是通过发送不同命令码,然后驱动返回不同的数据。
一、应用程序中的ioctl函数
头文件
#include <sys/ioctl.h>
函数原型
int ioctl(int fd, unsigned long request,...);
参数说明:
int fd-----文件描述符 open返回值
unsigned long request----依赖于设备的请求代码(对设备的请求方式、命令)
... ----->第三个参数可以有,也可以省略,要根据参数二来确定
比如:摄像头----videodev2.h--->usr/include/linux/videodev2.h4
#define VIDIOC_G_FMT _IOWR('V', 4, struct v412_format)---->从底层得到v412 format数据给应用层
#define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format)---->
ioctl(fd,VIDIOC_G_FMT,要第三个参数)
常用的两种:
int ioctl(int fd, unsigned long request); //应用程序下发命令给驱动
int ioctl(int fd, unsigned long request , unsigned long args); //应用程序下发命令和参数给驱动
二、驱动程序中的ioctl函数
long (*unlocked ioctl)(struct file *,unsigned int, unsigned long);
ioctl控制命令的形式
ioctl命令:IO,IOR,IOW,IOWR,但是,应用程序和驱动程序也要约定同一个形式,而且由驱动程序来确定。
在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下所示:
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
名称 | 比特位 | 含义 |
dir | 31-30 | 命令访问模式(数据传输方向),占据 2 bit 00 - 命令不带参数 |
type | 15-8 | 设备类型,占据 8 bit,可以为任意 char 型字符,例如灯-----> 'L', beep ----> 'B'等等,其主要作用是使 ioctl 命令有唯一的设备标识 |
nr | 7-0 | 命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增 |
size | 29-16 | 涉及到 ioctl 函数第三个参数 arg ,占据14bit,指定了 arg 的数据类型及长度 |
#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)))
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(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)
/* ...and for the drivers/sound files... */
#define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)
1)定义一个命令,该命令不需要参数
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
2)定义一个命令,该命令说明应用程序向驱动程序读参数
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
3)定义一个命令,该命令说明应用程序向驱动程序写参数
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
4)定义一个命令,参数的传输是双向的
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE, (type),(nr),(_IOC_TYPECHECK(size)))
5)获得传输方向位段的值
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
6)获得类型的值
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
7)获得编号的值
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
8)获得大小的值
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
应用程序如果打算下发命令给驱动程序,需要设计对应的命令码,并且驱动程序也必须有对应的命令码(相当于应用程序和驱动程序必须有一致的命令码)。
三、示例
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/device.h>
static struct class *class;
struct option{
unsigned int datab;
unsigned int parity;
unsigned int stopb;
};
#define VS_MAGIC 's'
#define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD _IOR(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT _IOR(VS_MAGIC, 3, struct option)
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
struct vser_dev{
unsigned int baud;
struct option opt;
struct cdev cdev;
};
DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;
static int vser_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int vser_release(struct inode *inode, struct file *flip)
{
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
int ret;
unsigned int copied = 0;
ret = kfifo_to_user(&vsfifo, buf, count, &copied);
return ret == 0 ? copied : ret;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
int ret;
unsigned int copied = 0;
ret = kfifo_from_user(&vsfifo, buf, count, &copied);
return ret == 0? copied : ret;
}
static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) != VS_MAGIC)
return -ENOTTY;
switch(cmd) {
case VS_SET_BAUD:
vsdev.baud = arg;
break;
case VS_GET_BAUD:
arg = vsdev.baud;
break;
case VS_SET_FFMT:
if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
return -EFAULT;
break;
case VS_GET_FFMT:
if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
return -EFAULT;
break;
default:
break;
};
return 0;
}
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
.unlocked_ioctl = vser_ioctl,
};
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;
cdev_init(&vsdev.cdev, &vser_ops);
vsdev.cdev.owner = THIS_MODULE;
vsdev.baud = 115200;
vsdev.opt.datab = 8;
vsdev.opt.parity = 0;
vsdev.opt.stopb = 1;
ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);
if (ret)
goto add_err;
/* 自动创建设备节点 */
class = class_create(THIS_MODULE, "my_ioctl"); /* /sys/class/my_ioctl */
device_create(class, NULL, dev, NULL, "vser0"); /* /dev/vser0 */
return 0;
add_err:
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
cdev_del(&vsdev.cdev);
device_destroy(class, dev);
class_destroy(class);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
应用层:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
struct option{
unsigned int datab;
unsigned int parity;
unsigned int stopb;
};
#define VS_MAGIC 's'
#define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD _IOR(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT _IOR(VS_MAGIC, 3, struct option)
int main(int argc, char *argv[])
{
int fd;
int ret;
unsigned int baud;
struct option opt = {8,1,1};
fd = open("/dev/vser0", O_RDWR);
if (fd == -1)
goto fail;
baud = 9600;
ret = ioctl(fd, VS_SET_BAUD, baud);
if (ret == -1)
goto fail;
ret = ioctl(fd, VS_GET_BAUD, baud);
if (ret == -1)
goto fail;
ret = ioctl(fd, VS_SET_FFMT, &opt);
if (ret == -1)
goto fail;
ret = ioctl(fd, VS_GET_FFMT, &opt);
if (ret == -1)
goto fail;
printf("baud rate:%d\n", baud);
printf("frame format: %d%d%d\n", opt.datab, opt.parity, opt.stopb);
close(fd);
exit(EXIT_SUCCESS);
fail:
perror("ioctl test");
exit(EXIT_FAILURE);
}
觉得有帮助的话,打赏一下呗。。
需要商务合作(定制程序)的欢迎私信!!