全部博文(298)
分类: LINUX
2011-03-24 12:34:19
(2)ioctl方法在驱动中的初次应用
注:所以文章红色字体代表需要特别注意和有问题还未解决的地方,蓝色字体表示需要注意的地方
1.本文所介绍的程序平台
开发板:arm9-mini2440
虚拟机为:Red Hat Enterprise Linux 5
开发板上系统内核版本:linux-2.6.32.2
ioctl方法介绍:
大部分驱动除了需要具备读写设备 的能力外,还需要具备对硬件控制的能力。例如,要求设备报告错误信息, 改变波特率,这些操作常常通过 ioctl方法来实现。
在用户空间,使用ioctl系统调用来控制设备,原型 如下:
int ioctl(int fd,unsigned long cmd,...)
原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第 2个参数 )是否涉及 到与设备的数据交互, 注意第三个参数是指针。
ioctl 驱动方法有和用户空间版本不同的原型:
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
cmd参数从用户空间传下来,可选的参数 arg 以一个unsigned long 的形 传递,不管它是一个整数或一个指针。如果cmd命令不涉及数据传输,则第 3个
参数arg的值无任何意义。
定义命令
在编写ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。ioctl命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:类型(幻数),序号,传送方向,参数的大小。Documentation/ioctl-number.txt文件中罗列了在内核中已经使用了的幻数。
定义 ioctl命令的正确方法是使用 4个位段,这个列表
中介绍的符号定义在
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成员作为参数被传递。
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 。
使用实例:
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg,
_IOC_SIZE(cmd));
//why _IOC_READ 对应 VERIFY_WRITE ???
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg,
_IOC_SIZE(cmd));
if (err)
return -EFAULT;
Ioctl函数实现(命令操作)
switch(cmd)
{
case MEM_IOCSQUANTUM: /* Set: arg points to the value */
retval = __get_user(scull_quantum, (int *)arg);
break;
case MEM_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int *)arg);
break;
default:
return -EINVAL;
}
2.程序清单
本次实验程序为国嵌培训代码,本人作了改动和较为详细的注释,如有错误请指出。
memdev.h
//头文件
#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#include
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0 /*预设的mem的主设备号*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*设备数*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
/*mem设备描述的结构体*/
struct mem_dev
{
char *data;
unsigned long size;
};
/*定义幻数*/
#define MEMDEV_IOC_MAGIC 'k'
/* 定义命令 ,这里的命令都是unsigned int类型*/
#define MEMDEV_IOCPRINT _IO(MEMDEV_IOC_MAGIC, 1)
#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)
#define MEMDEV_IOC_MAXNR 3 //定义命令的最大序列号
#endif
memdev.c
/*********************************************
*memdev.c
*********************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "memdev.h"
static int mem_major = MEMDEV_MAJOR;
module_param(mem_major,int,S_IRUGO);
struct mem_dev *mem_devp;/*设备结构体指针*/
struct cdev cdev;
/*文件打开函数*/
int mem_open(struct inode *inode,struct file *filp)
{
struct mem_dev *dev;
/*获取次设备号*/
int num = MINOR(inode->i_rdev);
/*大于最大的次设备号就错误了 因为注册的时候要指定次设备的数目*/
if(num >=MEMDEV_NR_DEVS)
return -ENODEV;
dev = &mem_devp[num];
/*将设备描述结构指针赋值给文件私有数据指针*/
filp->private_data = dev;
return 0;
}
/*文件释放函数*/
int mem_release(struct inode *inode,struct file *filp)
{
return 0;
}
/*读函数*/
static ssize_t mem_read(struct file *filp,char __user *buf,size_t size,loff_t *poss)
{
unsigned long p = *poss;
unsigned int count = size;
int ret = 0;
/*获得设备结构体指针由于read没有struct inode *inode结构 而且函数的
参数不能改 只能改函数名所以只能在open函数里面设置*/
struct mem_dev *dev = filp->private_data;
/*判断读位置是否有效*/
if(p >= MEMDEV_SIZE)
return 0;
if(count > MEMDEV_SIZE-p)
count = MEMDEV_SIZE-p;
/*读数据到用户空间*/
if(copy_to_user(buf,(void*)(dev->data+p),count))
{
ret = -EFAULT;
}
else
{
*poss +=count;
ret = count;
printk(KERN_INFO "read %d bytes from %lu\n",count,p);
}
return ret;
}
/*写函数*/
static ssize_t mem_write(struct file *filp,const char __user *buf,size_t size,loff_t *poss)
{
unsigned long p = *poss;
unsigned int count = size;
int ret=0;
struct mem_dev *dev = filp->private_data;
if(p>=MEMDEV_SIZE)
return 0;
if(count>MEMDEV_SIZE-p)
count = MEMDEV_SIZE-p;
if(copy_from_user(dev->data+p,buf,count))
{
ret = -EFAULT;
}
else
{
*poss += count;
ret = count;
printk(KERN_INFO "write %d bytes from %lu\n",count,p);
}
return ret;
}
int memdev_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int err = 0;
int ret = 0;
int ioarg = 0;
/* 检测命令的有效性 */
if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)//命令是否操作这一类设备
return -EINVAL;
if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) // 命令序列号是否超出定义
return -EINVAL;
/* 根据命令类型,检测参数空间是否可以访问 */
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
/* 根据命令,执行相应的操作 */
switch(cmd) {
/* 打印当前设备信息 */
case MEMDEV_IOCPRINT:
printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
break;
/* 获取参数 */
case MEMDEV_IOCGETDATA:
ioarg = 1101;
ret = __put_user(ioarg, (int *)arg);
break;
/* 设置参数 */
case MEMDEV_IOCSETDATA:
ret = __get_user(ioarg, (int *)arg);
printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
break;
default:
return -EINVAL;
}
return ret;
}
static loff_t mem_llseek(struct file *filp,loff_t offset,int whence)
{
loff_t newpos;
switch(whence)
{
case 0:
newpos = offset;
break;
case 1:
newpos = filp->f_pos + offset;
break;
case 2:
newpos = MEMDEV_SIZE - 1 + offset;
break;
default:
return -EINVAL;
}
if((newpos<0) || (newpos>MEMDEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
.llseek = mem_llseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
.ioctl = memdev_ioctl,/*为什么这里有‘,’??*/
};
/*设备驱动模块加载函数*/
static int memdev_init(void)
{
int result;
int i;
dev_t devno = MKDEV(mem_major,0);
/*静态申请设备号*/
if(mem_major)
result = register_chrdev_region(devno,2,"memdev");
else/*动态申请设备号*/
{
result = alloc_chrdev_region(&devno,0,2,"memdev");
mem_major = MAJOR(devno);
}
if(result < 0)
return result;
/*初始化cdev结构*/
cdev_init(&cdev,&mem_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &mem_fops;
/*注册字符设备*/
cdev_add(&cdev,MKDEV(mem_major,0),MEMDEV_NR_DEVS);
/*为设备描述结构分配内存*/
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev),GFP_KERNEL);
if(!mem_devp)/*申请失败*/
{
result = -ENOMEM;
goto fail_malloc;
}
memset(mem_devp,0,MEMDEV_NR_DEVS * sizeof(struct mem_dev));
/*为设备分配内存*/
for(i=0;i
{
mem_devp[i].size = MEMDEV_SIZE;
mem_devp[i].data = kmalloc(MEMDEV_SIZE,GFP_KERNEL);
memset(mem_devp[i].data,0,MEMDEV_SIZE);
}
return 0;
fail_malloc:
unregister_chrdev_region(devno,1);
return result;
}
static void memdev_exit(void)
{
cdev_del(&cdev);
kfree(mem_devp);
unregister_chrdev_region(MKDEV(mem_major,0),2);
}
MODULE_AUTHOR("Zechin Liao");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
memdevapp.c
/*********************************************
*memdevapp.c
*********************************************/
#include
#include
#include
#include
#include "memdev.h"
int main()
{
int fd;
int cmd;
int arg = 0;
char buf[4096];
/*初始化buf*/
strcpy(buf,"This is a example of charactar devices driver");
printf("buf:%s\n",buf);
/*打开设备文件*/
fd=open("/dev/memdev0",O_RDWR);
if(fd == -1)
{
printf("open memdev failed!\n");
return -1;
}
/*调用命令 MEMDEV_IOCPRINT*/
printf("call MEMDEV_IOCPRINT \n");
cmd = MEMDEV_IOCPRINT;
if(ioctl(fd, cmd, &arg) < 0)
{
printf("call cmd MEMDEV_IOCPRINT failed \n");
return -1;
}
/*调用命令 MEMDEV_IOCSETDATA*/
printf("call MEMDEV_IOCSETDATA \n");
cmd = MEMDEV_IOCSETDATA;
arg = 2007;
if(ioctl(fd, cmd, &arg) < 0)
{
printf("call cmd MEMDEV_IOCSETDATA failed \n");
return -1;
}
/*调用命令 MEMDEV_IOCGETDATA*/
printf("call MEMDEV_IOCGETDATA \n");
cmd = MEMDEV_IOCGETDATA;
if(ioctl(fd, cmd, &arg) < 0)
{
printf("call cmd MEMDEV_IOCGETDATA failed \n");
return -1;
}
printf("call MEMDEV_IOCGETDATA return %d \n", arg);
close(fd);
return 0;
}
Makefile
ifneq ($(KERNELRELEASE),)
obj-m:=memdev.o
else
KERNELDIR:=/root/mini2440/linux/linux-2.6.32.2
PWD:=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-
clean:
rm -rf *.ko *.o *.mod.c *.mod.o *.symvers
endif
注意:这里有一个问题没有解决如果我把Makefile文件改名为armMakefile的时候,使用命令make -f armMakefile 会出错,如果名字为Makefile则没有错误。错误代码为:
scripts/Makefile.build:44: /root/driver/char2/Makefile: No such file or directory
make[2]: *** No rule to make target `/root/driver/char2/Makefile'. Stop.
make[1]: *** [_module_/root/driver/char2] Error 2
make: *** [all] Error 2
Arm平台实验:
[root@FriendlyARM /udisk]# insmod memdev.ko
[root@FriendlyARM /udisk]# cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
180 usb
188 ttyUSB
189 usb_device
204 s3c2410_serial
253 memdev
254 rtc
Block devices:
259 blkext
7 loop
8 sd
31 mtdblock
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
[root@FriendlyARM /udisk]# mknod /dev/memdev0 c 253 0
[root@FriendlyARM /udisk]# ls -l /dev/memdev0
crw-r--r-- 1 root root 253, 0 Mar 19 21:40 /dev/memdev0
[root@FriendlyARM /udisk]# ./memdevapp
buf:This is a example of charactar devices driver
<--- CMD MEMDEV_IOCPRINT Done--->
<--- In Kernel MEMDEV_IOCSETDATA ioarg = 2007 --->
call MEMDEV_IOCPRINT
call MEMDEV_IOCSETDATA
call MEMDEV_IOCGETDATA
call MEMDEV_IOCGETDATA return 1101
[root@FriendlyARM /udisk]#