Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2422930
  • 博文数量: 298
  • 博客积分: 7876
  • 博客等级: 准将
  • 技术积分: 5500
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-23 13:39
文章存档

2013年(2)

2012年(142)

2011年(154)

分类: LINUX

2011-03-24 12:34:19

2ioctl方法在驱动中的初次应用

 

注:所以文章红色字体代表需要特别注意和有问题还未解决的地方,蓝色字体表示需要注意的地方

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(typenr)

没有参数的命令

_IOR(typenrdatatype)

从驱动中读数据

_IOW(typenrdatatype)

写数据到驱动

_IOWR(typenrdatatype)

双向传送,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]#

 

阅读(3014) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~