一、概述
一个设备除了能够通过读写操作来收发数据或返回、保存数据、还应该有很多其他的操作。
比如一个串口设备还应该具备波特率获取和设置、帧格式获取与设置的操作。
一个LED设备甚至不应该有读写操作,而应该具有点灯和灭灯的操作。
硬件设备如此众多,各种操作纷繁复杂,所以内核将读写之外的其他I/O操作都委派给了另外一个函数接口ioctl,而且文件I/O还具备多种模型,比如有非阻塞、阻塞、I/O多路复用、异步I/O和异步通知。
二、IOCTL函数的原型
1. 应用程序的ioctl()—>linux的系统IO函数
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
经常使用的函数原型:
int ioctl(int fd, unsigned int cmd, unsigned long args);
int ioctl(int fd, unsigned int cmd);
应用程序的ioctl()会系统调用驱动程序的unlocked_ioctl()
2. 驱动程序的ioctl()—>file_operations
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long args);
3. 应用的方法
1)应用程序向驱动程序发送命令cmd,但是不发送参数
int ioctl(int fd, unsigned int cmd);
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long args);
注:此时unsigned long args会被忽略
2)应用程序向驱动程序发送命令,并发送参数
int ioctl(int fd, unsigned int cmd, unsigned long args);
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long args);
unsigned int cmd —>应用程序发下来的命令
unsigned long args —>应用程序发下来的参数
例子:
#include <linux/videodev2.h>
#include <linux/ioctl.h>
struct v4l2_format fmt; //保存或设置摄像头输出的视频格式
fmt.fmt.pix.width = 640;//设置视频输出宽为640
fmt.fmt.pix.height = 480;//输出高位480
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//设置输出格式为YUYV格式(必须要摄像头支持YUYV格式设置才有效)
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(fd, VIDIOC_S_FMT, &fmt); //把格式数据设置到驱动中
if (ret < 0)
{
printf("VIDIOC_S_FMT failed (%d)\n", ret);
return ret;
}
应用程序向驱动程序发送命令VIDIOC_S_FMT,并发送参数fmt。
3)应用程序向驱动程序发送命令,并获取参数
int ioctl(int fd, unsigned int cmd, unsigned long args);
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long args);
unsigned int cmd —>应用程序发下来的命令
unsigned long args —>发送给应用程序的参数
例子:
#include <linux/ioctl.h>
#include <linux/videodev2.h>
struct v4l2_format fmt; //保存或设置摄像头输出的视频格式
memset(&fmt, 0, sizeof(struct v4l2_format));
ret = ioctl(fd, VIDIOC_G_FMT, &fmt);//从驱动中获取信息
if (ret < 0) {
printf("***********************************\nVIDIOC_G_FMT failed (%d)\n", ret);
return ret;
}
printf("Stream Format Informations:\n");
printf(" type: %d\n", fmt.type);
printf(" width: %d", fmt.fmt.pix.width);
printf(" height: %d\n", fmt.fmt.pix.height);
应用程序向驱动程序发送命令VIDIOC_G_FMT,获取驱动的数据fmt
三、IOCTL的命令
头文件:#include <linux/ioctl.h>
IOCTL的命令需要预先定义好,应用程序和驱动程序要使用相同的命令。命令是一个32位无符号的整型值,该整型值是有固定格式要求的。
我们要使用固定的格式标准,定义自己的cmd。
其实每个命令就是32位的,通过配置该32位的命令来形成_IO、_IOR、_IOW、_IOWR。
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
定义一个命令,但是不需要参数参数
#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_TYPECH
函数的参数说明:
type,命令的类型,一般为一个ASCII码,一般一个驱动程序使用一个type
nr,该命令下序号。一个驱动有多个命令,一般它们的type,序号不同
size,args的类型
#define STK_IOCTL_SET_ENABLE _IOW(STKDIR, 0x03, char)
#define STK_IOCTL_SET_OFFSET _IOW(STKDIR, 0x07, char[3])
#define PHONE_CAPABILITIES_CHECK _IOW ('q', 0x82, struct phone_capability *)
#define PHONE_RING_CADENCE _IOW ('q', 0x86, short)
例子:
应用程序定义的cmd:/usr/include/linux/videodev2.h
#define VIDIOC_G_FMT _IOWR('V', 4, struct v4l2_format)
#define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format)
驱动程序定义的cmd:内核源码/include/linux/videodev2.h
#define VIDIOC_G_FMT _IOWR('V', 4, struct v4l2_format)
#define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format)
四、命令示例
1.定义LED灯的命令
应用程序发命令:灯亮和灯灭
应用程序发参数:灯号:8/9/10/11
由于开发板有4盏LED灯,最好使用参数来指定控制哪一盏LED灯
#define GEC6818_LED_ON _IOW('L', 1, unsigned long)
#define GEC6818_LED_OFF _IOW('L', 2, unsigned long)
2.定义蜂鸣器的命令
由于开发板只有1个蜂鸣器,可以只用单独一个命令来控制蜂鸣器,不用附带参数
#define GEC6818_BEEP_ON _IO('B', 1)
#define GEC6818_BEEP_OFF _IO('B', 2)
3.定义按键的命令
读取单个按键
#define GEC6818_K2_STA _IOR('K', 1, unsigned long)
#define GEC6818_K3_STA _IOR('K', 2, unsigned long)
#define GEC6818_K4_STA _IOR('K', 3, unsigned long)
#define GEC6818_K6_STA _IOR('K', 4, unsigned long)
读取所有按键
#define GEC6818_KALL_STA _IOR('K', 5, unsigned long)
五、参数内容多字节传递
如果实现参数内容多字节传递,应用层要传递地址值。驱动层得使用copy_from_user或copy_to_user,实现数据内容的交换,参考源码:
应用层:
#define CMD_BUF_W _IOW('B',0,unsigned char[4])
#define CMD_BUF_R _IOR('B',1,unsigned char[4])
unsigned char buf[4]={1,2,3,4};
ioctl(fd,CMD_BUF_W,buf);
ioctl(fd,CMD_BUF_R,buf);
驱动层:
#define CMD_BUF_W _IOW('B',0,unsigned char[4])
#define CMD_BUF_R _IOW('B',1,unsigned char[4])
long key_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *) arg;
unsigned int value=0x12345678;
switch (cmd)
{
case CMD_BUF_W:
{
copy_from_user((void *)&value, argp, sizeof(unsigned int))
}break;
case CMD_BUF_R:
{
copy_to_user(argp, (const void *)&value, sizeof(unsigned int))
}break;
default:return -ENOIOCTLCMD;
}
return 0;
}