ioctl命令详解
昨天复习了一下对于ioctl 的使用和实现
举个例子 吧。
/* ioctl command for BMA220 device file
*/
static long bma220_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
int err = 0;
unsigned char data[6];
struct bma220_data *sensor_data = i2c_get_clientdata(bma220_client);
/* check cmd */
if (_IOC_TYPE(cmd) != BMA220_IOC_MAGIC) {
#if DEBUG
printk(KERN_ERR "cmd magic type error\n");
#endif
return -ENOTTY;
}
if (_IOC_NR(cmd) > BMA220_IOC_MAXNR) {
#if DEBUG
printk(KERN_ERR "cmd number error\n");
#endif
return -ENOTTY;
}
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,
(void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,
(void __user *)arg, _IOC_SIZE(cmd));
if (err) {
#if DEBUG
printk(KERN_ERR "cmd access_ok error\n");
#endif
return -EFAULT;
}
/* check bam120_client */
if (bma220_client == NULL) {
#if DEBUG
printk(KERN_ERR "I2C driver not install\n");
#endif
return -EFAULT;
}
/* cmd mapping */
switch (cmd) {
case BMA220_IOCTL_EVENT_CTRL:
if (copy_from_user(data, (unsigned char *)arg, 1) != 0) {
#if DEBUG
printk(KERN_ERR "copy_from_user error\n");
#endif
return -EFAULT;
}
printk(KERN_INFO "%s: set bma220_no_event = %d\n",
__func__, data[0]);
bma220_no_event = (int)data[0];
return err;
case BMA220_IOCTL_CALIBRATION:
if (sensor_data->enabled == 0) {
bma220_set_suspend_mode(1);
bma220_i2c_delay(30);
err = bma220_reset_offset_xyz();
if (err)
printk(KERN_ERR "%s: reset offset error!\n",
__func__);
else
err = bma220_calibration();
bma220_i2c_delay(30);
bma220_set_suspend_mode(0);
return err;
}
err = bma220_reset_offset_xyz();
if (err)
printk(KERN_ERR "%s: reset offset error!\n", __func__);
else
err = bma220_calibration();
return err;
case BMA220_IOCTL_WRITE:
if (copy_from_user(data, (unsigned char *)arg, 2) != 0) {
#if DEBUG
printk(KERN_ERR "copy_from_user error\n");
#endif
return -EFAULT;
}
printk(KERN_INFO "%s: write = %x %x\n",
__func__, data[1], data[0]);
err = bma220_i2c_write(BMA220_I2C_ADDR, data[1], &data[0], 1);
return err;
case BMA220_IOCTL_READ:
if (copy_from_user(data, (unsigned char *)arg, 1) != 0) {
#if DEBUG
printk(KERN_ERR "copy_from_user error\n");
#endif
return -EFAULT;
}
err = bma220_i2c_read(BMA220_I2C_ADDR, data[0], &data[0], 1);
printk(KERN_INFO "%s: read = %d\n", __func__, data[0]);
if (copy_to_user((unsigned char *)arg, data, 1) != 0) {
#if DEBUG
printk(KERN_ERR "copy_to_user error\n");
#endif
return -EFAULT;
}
return err;
case BMA220_SOFT_RESET:
err = bma220_soft_reset();
return err;
case BMA220_SET_SUSPEND:
if (copy_from_user(data, (unsigned char *)arg, 1) != 0) {
#if DEBUG
printk(KERN_ERR "copy_from_user error\n");
#endif
return -EFAULT;
}
bma220_set_suspend_mode((int)data[0]);
return err;
(signed char *)data, 1) != 0) {
#if DEBUG
printk(KERN_ERR "copy_to_user error\n");
#endif
return -EFAULT;
}
return err;
case BMA220_READ_ACCEL_Y:
err = bma220_read_accel_y((signed char *)data);
if (copy_to_user((signed char *)arg,
(signed char *)data, 1) != 0) {
#if DEBUG
printk(KERN_ERR "copy_to_user error\n");
#endif
return -EFAULT;
}
return err;
case BMA220_READ_ACCEL_Z:
err = bma220_read_accel_z((signed char *)data);
if (copy_to_user((signed char *)arg,
(signed char *)data, 1) != 0) {
#if DEBUG
printk(KERN_ERR "copy_to_user error\n");
#endif
return -EFAULT;
}
return err;
case BMA220_SET_EN_LOW:
if (copy_from_user(data, (unsigned char *)arg, 1) != 0) {
#if DEBUG
printk(KERN_ERR "copy_from_user error\n");
#endif
return -EFAULT;
}
err = bma220_set_en_low(*data);
return err;
case BMA220_SET_EN_HIGH_XYZ:
printk(KERN_ERR "copy_from_user error\n");
#endif
return -EFAULT;
}
err = bma220_set_en_tt_xyz(*data);
return err;
命令号的定义
#define BMA220_IOC_MAGIC 'B'
#define BMA220_SET_SLEEP_EN \
_IOWR(BMA220_IOC_MAGIC, 0, unsigned char)
#define BMA220_SET_SUSPEND \
_IOW(BMA220_IOC_MAGIC, 1, unsigned char)
#define BMA220_READ_ACCEL_X \
_IOWR(BMA220_IOC_MAGIC, 2, signed char)
#define BMA220_READ_ACCEL_Y \
_IOWR(BMA220_IOC_MAGIC, 3, signed char)
#define BMA220_READ_ACCEL_Z \
_IOWR(BMA220_IOC_MAGIC, 4, signed char)
#define BMA220_SET_MODE \
_IOWR(BMA220_IOC_MAGIC, 5, unsigned char)
#define BMA220_GET_MODE \
_IOWR(BMA220_IOC_MAGIC, 6, unsigned char)
#define BMA220_SET_RANGE \
_IOWR(BMA220_IOC_MAGIC, 7, unsigned char)
#define BMA220_GET_RANGE \
_IOWR(BMA220_IOC_MAGIC, 8, unsigned char)
#define BMA220_SET_BANDWIDTH \
_IOWR(BMA220_IOC_MAGIC, 9, unsigned char)
#define BMA220_GET_BANDWIDTH \
_IOWR(BMA220_IOC_MAGIC, 10, unsigned char)
#define BMA220_SET_SC_FILT_CONFIG \
_IOWR(BMA220_IOC_MAGIC, 11, unsigned char)
#define BMA220_RESET_INTERRUPT \
_IO(BMA220_IOC_MAGIC, 12)
#define BMA220_GET_DIRECTION_STATUS_REGISTER \
_IOWR(BMA220_IOC_MAGIC, 13, unsigned char)
#define BMA220_GET_INTERRUPT_STATUS_REGISTER \
_IOWR(BMA220_IOC_MAGIC, 14, unsigned char)
..................
#define BMA220_IOC_MAXNR 100
功能:
大部分驱动除了需要具备读写设备的能力外,还需要具备对硬件控制的能力。例如,要求设备报告错误信息,改变波特率,这些操作常常通过ioctl方法来实现。
用户使用方法:
在用户空间,使用ioctl系统调用来控制设备,原型如下:
int ioctl(int fd,unsigned long cmd,...);
原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第二个参数)是否涉及到与设备的数据交互。
驱动ioctl方法:
ioctl驱动方法有和用户空间版本不同的原型:
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
cmd参数从用户空间传下来,可选的参数 arg以一个unsigned long 的形式传递,不管它是一个整数或一个指针。如果cmd命令不涉及数据传输,则第3个参数arg的值无任何意义。
ioctl实现
如何实现ioctl方法?
1.定义命令
2.实现命令
定义命令
在编写ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的中定义。ioctl命令编码被划分为几个位段,include/asm/ioctl.h定义了这些位字段:类型(幻数),序号,传送方向,参数的大小。
Documentation/ioctl-number.txt文件中罗列了在内核中已经使用了的幻数。
定义命令
定义ioctl命令的正确方法是使用4 个位段,这个列表中介绍的符号定义在<linux/ioctl.h>中:
Type
幻数(类型):表明哪个设备的命令,在参考了ioctl-number.txt之后选出,8位宽。
Number
序号,表明设备命令中的第几个,8位宽。
Direction
数据传送的方向,可能的值是_IOC_NONE(没有数据传输),_IOC_READ,_IOC_WRITE。数据传送是从应用程序的观点来看待的,_IOC_READ意思是从设备读。
size
用户数据的大小。(13/14位宽,视处理器而定)
内核提供了下列宏来帮助定义命令:
_IO(type,nr);
没有 参数的命令
_IOR(type,nr,datatype)
从驱动中读取数据
_IOW(type,nr,datatype)
写数据到驱动
_IOWR(type,nr,datatype)
双向传送,type和number成员作为参数被传递。
列子(example)
#define MEM_IOC_MAGIC 'm' //定义幻数
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC,1,int)
ioctl函数实现
定义好了命令,下一步就是要实现ioctl函数了,ioctl函数的实现包括如下3个技术环节:
1.返回值
2. 参数使用
3.命令操作
ioctl函数实现(返回值)
ioctl函数的实现通常是根据命令执行的一个 switch语句。但是,当命令号不能匹配任何一个设备所支持的命令时, 通常返回-EINVAL("非法参数")
ioctl函数实现(参数)
如何使用ioctl中的参数?
如果是一个整数,可以直接使用。如果是指针,我们必须确保这个用户地址是有效的,因此使用前需进行正确的检查。
ioctl函数实现(参数检查)
不需要检测:
copy_from_user
copy_to_user
get_user
put_user
需要检测:
__get_user
__put_user
参数检查函数:
int access_ok(int type,const void *addr,unsigned long size);
第一个参数是VERIFY_READ或者VERIFY_WRITE,用来表明是读用户内存还是写用户内存。addr参数是要操作的用户内存地址,size是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数等于sizeof(int)。
access_ok返回一个布尔值:1是成功(存取没问题)和0是失败(存取有问题),如果该函数返回失败,则ioctl应当返回-EFAULT。
ioctl 函数实现(参数检查)
if(_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd));
//为什么_IOC_READ对应VERIFY_WRITE??
else if(_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,(void _user)*arg,_IOC_SEIZE(cmd));
if(err)
return -EFAULT;
ioctl函数实现(命令操作)
switch(cmd)
{
case MEM_IOCSQUANTUM:
retval = _get_user(scull_quantum,(int *)arg);
break;
case MEM_IOCGQUANTUM:
retval = _put_user(scull_quantum,(int *)arg);
break;
default:
return -EINVAL;
}