zoukankan      html  css  js  c++  java
  • 字符设备驱动1:新的方式添加cdev + 在open函数中将文件私有数据指向设备结构体

    本例中,驱动入口处,使用cdev_add添加驱动,这点也可与字符设备驱动0:一个简单但完整的字符设备驱动程序对比一下。

        另外主要讲xx_open实现文件私有数据指向设备结构体。

    引子:

    偶然看到,在jz2440韦东山写的一个led驱动中,open函数仅对硬件做了初始化(每次open之后默认打开led灯,这不是我期望的),而且没有将文件私有数据指向设备结构体。

    <1>基于此,在测试过程中,我试着不实现驱动的xx_open函数,在app中,仍使用open(filename, O_RDWR)打开设备并随后对其进行ioctl以及read等操作,发现可以正常操作led灯。见文中的实例。

      说明在驱动中不实现xx_open,在应用程序里面还是可以通过open打开该设备。

      实现“dev = container_of(inode->i_cdev , struct xx_dev ,cdev);    filp->private_data = dev ; ”  这样的代码,主要是增加了一层封装和注册,便于结构化代码。

    <2>而作为对比,在《linux设备驱动开发详解》中的一个驱动,实现了open,并将文件私有数据指向设备结构体。

    int xx_open(struct inode inode ,struct file* filp)
    {
        struct xx_dev *dev;
        dev = container_of(inode->i_cdev , struct xx_dev ,cdev);
        filp->private_data = dev ;
        return 0;
    }

    实现open与否,肯定是有不同的,具体不同体现在哪里呢?

      可以看出,对驱动的xx_read、xx_write操作时,定位设备的方式产生了影响。

      不过,若xx_open函数没有实现 “dev = container_of(inode->i_cdev , struct xx_dev ,cdev);    filp->private_data = dev ; ”  ,那实现xx_open与否,设备定位的方式也没什么两样。

      为了保持规范性,xx_open是应该实现的;有时候为了简单,就不去实现这些麻烦的封装。

    _______________________________________________________________________________

     那么,open实现与否有何影响?

    QQ 潘老师 12:45:31
    字符设备框架中open接口是必须实现的
    原因如下:
    1、一切皆是"文件",字符设备也是"文件"
    2、要操作一个文件必须先open,才能read/write
    字符设备的原理如下:
    1、cdev表征一个字符设备对象
    通过cdev_alloc构造,cdev_init初始化好
    然后用cdev_add把这个对象插入内核管理数据区(链表,插入节点)
    2、mknod创建设备文件
    就是在VFS树形结构中创建了一个节点inode
    inode根据类型,字符设备的默认open方法仅仅查找cdev对象节点
    3、open过程
    open通过设备文件查找到inode,并回调inode中的默认打开方面找到cdev对象,这样就找到了cdev中封装的file_operations,即操作方法函数集合,同时open过程会创建一个file对象(原因是open的时候有标志:譬如只读、只写、阻塞等等,两次打开的时候标志可以不一致,则每次创建一个file对象来抽象"文件",实际就是存储这些标志,并保存指向cdev中保存的file_operations方法)
    4、read/write过程
    通过文件描述符找到open时创建的file对象,就找到了open时初始化好的file中指向file_operations,则找到了驱动中对应的操作函数
    百度知道Wu_Roc
    如果不实现open的话,驱动会默认设备的打开永远成功。打开成功时open返回0。
    内核里是若open函数未定义的话,会跳过这个函数。但是其他步骤不变。
    关键代码:__dentry_open函数里
    ...
    if (open) {
        error = open(inode, f);
        if (error)
            goto cleanup_all;
    }
    ...
    所以sys_open调用的话会依旧照常进行。如果你定义了open,他就会调用的你写的open,如果没定义,就跳过这一步。
    这里说明不实现open是允许的。
    

    ___________________________________________________________

    经过多方对比参考,在网上一篇文章找到了想要的答案。[1]

    大多数linux驱动工程师都遵循一个"潜规则",那就是将文件的私有的数据private_data指向设备结构体,在read(),write,ioctl(),llseek等函数通过private_data访问设备结构体。一般,我们在open里,讲设备结构体赋值给文件私有数据指针,然后我们在ioctlreadwrite等函数里面,通过filep找到设备结构体,并对设备进行操作。代码如下:

    struct globalmem_dev{
     struct cdev cdev;//cdev结构体,用于描述设备的结构体
     unsigned char mem[GLOBALMEM_SIZE];//全局内存
    };//自定义设备结构体
    static struct globalmem_dev *globalmem_devp;//指向设备的结构体指针
    int globalmem_open(struct inode *inode,struct file *filp)
    {
        filp->private_data = globalmem_devp;//将设备结构体指针赋给文件私有数据指针,也就是将文件的私有的数据private_data指向设备结构体
        return 0;
    }
    //设备控制函数
    static int globalmem_ioctl(struct inode *inodep,struct file *filp,unsigned int cmd,unsigned long arg)
    {
      struct globalmem_dev *dev = filp->private_data;//获得设备结构体指针
    switch (cmd) {
     case MEM_CLEAR :
      memset(dev->mem,0,GLOBALMEM_SIZE);//清除全局内存
      printk(KERN_INFO "globalmem is set to zero
    ");
      break;
     
     default :
       return -EINVAL;
    }
    return 0;
    }

    从中可以看出,open的实现结合readwrite,实现了一种规范化的操作。在用户程序中,我们通过filename找到设备的inode或者filep,然后找到设备,并对其进行读写和控制。

    第一步:filename-->inode/filep(在用户程序中,我们通过filename找到设备的inode或者filep的具体过程不清楚,猜测是udev、sysfs文件系统完成的,待补充。)
    第二步:struct globalmem_dev *dev=filp->private_data;
    第三步:MINOR(dev->cdev->devno)得到minor。
    第四步:switch(minor){case xx:.....}。对比下面紧接着的代码[韦东山的代码],这似乎是在其上加了一层封装。

    在韦东山的代码中,因为没有实现open,使用了这样的方式操作设备:

    在app中,通过filename(借助udev,sysfs 这样的文件系统以及class的管理[2])识别子设备号,直接通过设备号对设备进行操作。

    第一步:filename-->minor
    第二步:minor = MINOR(inode->i_rdev);
    或minor = MINOR(filp->f_dentry->d_inode->i_rdev);
    第三步:switch(minor){….. }
    这样的话,是可以正常操作led灯的,只是这样没有利用filp->private_data这样的系统给我们预设的变量。(这样可能在需要用到filp->private_data的功能中会有所欠缺吧?具体的还不清楚。或者说不符合人们的操作习惯)
    ——————————————————————————————————————————————————————————————————————————————————————
    附代码:
    不实现file_operations.open函数的led驱动
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/cdev.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <asm/uaccess.h>
    #include <asm/irq.h>
    #include <asm/io.h>
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
     
    //static int leds_major = 237 ;//指定默认分配主设备号为237
    static int leds_major = 0 ;//不指定主设备号
    
    #define LEDS_DEV_NAME "leds_dev_name"
    #define LEDS_BASE_MINOR 0
    #define LEDS_DEV_COUNT 4
    /* bit0<=>D10, 0:亮, 1:灭
     * bit1<=>D11, 0:亮, 1:灭
     * bit2<=>D12, 0:亮, 1:灭
     */
    static char leds_status = 0x0 ;
    static DECLARE_MUTEX(leds_lock) ;
     
    static struct class *leds_class ;
    static struct class_device *leds_class_devs[4] ;
     
    typedef struct cdev LEDS_DEV_ST ;
    LEDS_DEV_ST *leds_cdev ;
     
    //成功时,返回读取的字节数。
    //失败返回一个负值。
    static int s3c24xx_leds_read(struct file* filp , char __user *buff , size_t count,loff_t *offp)
    {
        int minor = MINOR(filp->f_dentry->d_inode->i_rdev);
        char val;
        printk("info new: in s3c24xx_leds_read!
    ");
        switch ( minor )
        {
            case 0 ://minor==0 : leds all
                copy_to_user(buff ,&leds_status, 1);
                break;
            case 1 :
                down(&leds_lock);
                val = leds_status & 0x1;
                up(&leds_lock);
                copy_to_user(buff ,&val, 1);
                break;
            case 2 :
                down(&leds_lock);
                val = (leds_status>>1) & 0x1;
                up(&leds_lock);
                copy_to_user(buff, (const void *)&val, 1);
                break;
            case 3 :
                down(&leds_lock);
                val = (leds_status>>2) & 0x1;
                up(&leds_lock);
                copy_to_user(buff, (const void *)&val, 1);
                break;
     
            default:
                return -EFAULT;
        }
     
        return 1;
    }
    
    //可用write操作led灯。
    //buf 0/1 来自用户的开/关指令
    //app中:fd = open(filename, O_RDWR);  write(fd, &val, 1);
    //filename = leds/led0/led1/led2 , 见s3c24xx_leds_init 的 class_device_create .
    static ssize_t s3c24xx_leds_write(struct file *filp ,const char __user *buf ,size_t count ,loff_t *ppos)
    {
       int minor = MINOR(filp->f_dentry->d_inode->i_rdev);
        char val;
     
        copy_from_user(&val, buf, 1);
     
        switch (minor)
        {
            case 0: /* /dev/leds */
            {
                s3c2410_gpio_setpin(S3C2410_GPF4, (val & 0x1));
                s3c2410_gpio_setpin(S3C2410_GPF5, (val & 0x1));
                s3c2410_gpio_setpin(S3C2410_GPF6, (val & 0x1));
     
                down(&leds_lock);
                leds_status = val;
                up(&leds_lock);
                break;
            }
     
            case 1: /* /dev/led1 */
            {
                s3c2410_gpio_setpin(S3C2410_GPF4, val);
     
                if (val == 0)
                {
                    down(&leds_lock);
                    leds_status &= ~(1<<0);
                    up(&leds_lock);
                }
                else
                {
                    down(&leds_lock);
                    leds_status |= (1<<0);
                    up(&leds_lock);
                }
                break;
            }
     
            case 2: /* /dev/led2 */
            {
                s3c2410_gpio_setpin(S3C2410_GPF5, val);
                if (val == 0)
                {
                    down(&leds_lock);
                    leds_status &= ~(1<<1);
                    up(&leds_lock);
                }
                else
                {
                    down(&leds_lock);
                    leds_status |= (1<<1);
                    up(&leds_lock);
                }
                break;
            }
     
            case 3: /* /dev/led3 */
            {
                s3c2410_gpio_setpin(S3C2410_GPF6, val);
                if (val == 0)
                {
                    down(&leds_lock);
                    leds_status &= ~(1<<2);
                    up(&leds_lock);
                }
                else
                {
                    down(&leds_lock);
                    leds_status |= (1<<2);
                    up(&leds_lock);
                }
                break;
            }
     
        }
     
        return 1;//len
    }
    //可用 ioctl 操作led灯。
    //cmd 0/1 来自用户的开/关指令
    //app中:fd = open(filename, O_RDWR);  ioctl(fd,cmd);
    //filename = /dev/leds(led0,led1,led2) , 名字和s3c24xx_leds_init 的 class_device_create中保持一致 .
    static int s3c24xx_leds_ioctl(struct inode * inode, struct file *filp, unsigned int cmd, unsigned long arg)
    {
        #if 0
        int minor = MINOR(inode->i_rdev);  //ok 
        #else
        int minor = MINOR(filp->f_dentry->d_inode->i_rdev);//ok: filp->f_dentry->d_inode->i_rdev == inode->i_rdev
        #endif
        printk("info new: in s3c24xx_leds_ioctl!
    ");
       switch(minor)
       {
            case 0: /* /dev/leds */
            {
                // 配置3引脚为输出
                s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
                s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
                s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
     
                // 都输出0
                s3c2410_gpio_setpin(S3C2410_GPF4, cmd);
                s3c2410_gpio_setpin(S3C2410_GPF5, cmd);
                s3c2410_gpio_setpin(S3C2410_GPF6, cmd);
     
                down(&leds_lock);
             if(0==cmd){
                leds_status =0 ;
             }else if(1==cmd){
                leds_status = (1<<0)|(1<<1)|(1<<2);//cmd==0 是开。
             }
                up(&leds_lock);
                break;
            }
     
            case 1: /* /dev/led1 */
            {
                s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
                s3c2410_gpio_setpin(S3C2410_GPF4, cmd);
     
                down(&leds_lock);
             if(0==cmd){
                leds_status &= ~(1<<0);
             }else if(1==cmd){
                leds_status |= (1<<0);
             }
                up(&leds_lock);
                break;
            }
     
            case 2: /* /dev/led2 */
            {
                s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
                s3c2410_gpio_setpin(S3C2410_GPF5, cmd);
     
                if(0==cmd){
                leds_status &= ~(1<<1);
             }else if(1==cmd){
                leds_status |= (1<<1);
             }
                break;
            }
     
            case 3: /* /dev/led3 */
            {
                s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
                s3c2410_gpio_setpin(S3C2410_GPF6, cmd);
     
                down(&leds_lock);
             if(0==cmd){
                leds_status &= ~(1<<2);
             }else if(1==cmd){
                leds_status |= (1<<2);
             }
                up(&leds_lock);
                break;
            }
     
          default:
             return -EINVAL ;
       }
        return 0;
    }
     
     
    static struct file_operations s3c24xx_leds_fops ={
        .owner = THIS_MODULE ,
        //.open = s3c24xx_leds_open,
        .read = s3c24xx_leds_read ,
        .write = s3c24xx_leds_write ,
        .ioctl = s3c24xx_leds_ioctl
    };
     
    static int __init s3c24xx_leds_init()
    {
        int ret ;
        int minor = 0 ;
        printk("	 init :  leds_major=%d
    " ,leds_major);
       leds_cdev = (LEDS_DEV_ST*)kmalloc(sizeof(LEDS_DEV_ST),GFP_KERNEL) ;
       if(!leds_cdev){
          ret = -ENOMEM;
          goto fail_exit;
       }
        dev_t devno = MKDEV(leds_major , 0);
    /*申请设备号,当xxx_major不为0时,表示静态指定;当为0时,表示动态申请*/
        if(leds_major){
            ret = register_chrdev_region(devno , LEDS_DEV_COUNT , LEDS_DEV_NAME); //register_chrdev_region若成功,返回值0
            printk("	 reg :devno=%d , leds_major=%d
    ",devno,leds_major);
        }else{
            ret = alloc_chrdev_region(&devno, LEDS_BASE_MINOR, LEDS_DEV_COUNT, LEDS_DEV_NAME);
            leds_major = MAJOR(devno);
            printk("	 reg :devno=%d , leds_major=%d
    ",devno,leds_major);
        }
        if(ret<0){
            goto fail_register_chrdev_region;
        }
     
    //初始化并添加cdev结构体
        cdev_init(leds_cdev , &s3c24xx_leds_fops );
        leds_cdev->owner = THIS_MODULE ;
        leds_cdev->ops = &s3c24xx_leds_fops;
        ret = cdev_add(leds_cdev , devno , LEDS_DEV_COUNT);
     
        if(ret){
            printk(LEDS_DEV_NAME"Error %d adding leds_cdev",ret);
          ret = -EFAULT;
          goto fail_cdev_add;
        }
      
    //oo00 :begin : 分配了四个子设备号 minor == 0 1 2 3
    //class_create动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加到内核中。创建的逻辑类位于/sys/class/。
       leds_class = class_create(THIS_MODULE, "leds_class"); // /sys/class/下的类名
       if (IS_ERR(leds_class)){
           ret = PTR_ERR(leds_class);
           goto fail_class_create;
        }
     
       for (minor = 0; minor < LEDS_DEV_COUNT ; minor++){
          leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(leds_major, minor), NULL, (minor==0)?"leds":"led%d", minor);
          if (unlikely(IS_ERR(leds_class_devs[minor]))){
                ret = PTR_ERR(leds_class_devs[minor]);
                goto fail_class_device_create;
            }
       }
    //oo00 :end
     
    //device_create  or class_device_create ?  
    //device_destroy or class_device_unregister ?
    //答:均可。
    
        printk(LEDS_DEV_NAME" initialized
    ");
        return 0;
     
    fail_class_device_create:
        for( minor = 0;minor<LEDS_DEV_COUNT;minor++){
            class_device_unregister(leds_class_devs[minor]);
        }
        class_destroy(leds_class);
    fail_class_create:
        cdev_del(leds_cdev); //删除结构体
    fail_cdev_add:
    fail_register_chrdev_region:
        kfree(leds_cdev);
    fail_exit:
        return ret ;
    }
     
    static void __exit s3c24xx_leds_exit()
    {
        dev_t devno = MKDEV(leds_major , 0);
        int minor;
        for( minor = 0;minor<LEDS_DEV_COUNT;minor++){
            class_device_unregister(leds_class_devs[minor]);//device_destroy(leds_class,devno);
        }
        class_destroy(leds_class);
     
        cdev_del(leds_cdev);//删除结构体
        unregister_chrdev_region(devno, LEDS_DEV_COUNT);//注销设备区域
        kfree(leds_cdev);
        printk("exit:devno=%d,leds_major=%d
    " , devno,leds_major);
    }
    module_init(s3c24xx_leds_init);
    module_exit(s3c24xx_leds_exit);
     
    MODULE_AUTHOR("http://www.100ask.net");
    MODULE_VERSION("0.1.0");
    MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
    MODULE_LICENSE("GPL");

       对应的操作led的用户程序:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    /*
      *  ledtest <dev> <on|off>
      */
    
    void print_usage(char *file)
    {
        printf("Usage:
    ");
        printf("%s <dev> <on|off>
    ",file);
        printf("eg. 
    ");
        printf("%s /dev/leds on
    ", file);
        printf("%s /dev/leds status
    ", file); //status {bit0~2:led1~led3;  bit==0:led on}
        printf("%s /dev/leds off
    ", file);
        printf("%s /dev/led1 on
    ", file);
        printf("%s /dev/led1 off
    ", file);
    }
    
    int main(int argc, char **argv)
    {
        int fd;
        char* filename;
        char val;
        char string[100]={0};
        char *pstring;
        if (argc != 3)
        {
            print_usage(argv[0]);
            return 0;
        }
    
        filename = argv[1];
    
        fd = open(filename, O_RDWR);
        if (fd < 0)
        {
            printf("error, can't open %s
    ", filename);
            return 0;
        }
    
        if (!strcmp("on", argv[2]))
        {
            // 亮灯
            val = 0;
            ioctl(fd, 0);
        }
        else if(!strcmp("off", argv[2])){
            // close
            val = 1;
            ioctl(fd,1);
        }
        else if (!strcmp("status", argv[2]))
        {
            // read status
            val = 1; 
            read(fd, &val, 1); 
            printf("1111 status val = %x
    ",val); 
        }
        else
        {
            print_usage(argv[0]);
            return 0;
        }
        
    
        return 0;
    }

    mdev自动创建NODE:

      class_create// malloc + init + class_register;
      class_destroy//- destroys a struct class structure; the pointer to be destroyed must have been created with a call to class_create().
      class_device_destroy // - removes a class device that was created with class_device_create()
      class_device_create // malloc + init + class_device_register; creates a class device and registers it with sysfs

    参考:

    1. 使用文件私有数据的globalmem设备驱动 
      http://blog.sina.com.cn/s/blog_95268f5001015bkd.html

    2.借助udev,sysfs 文件系统以及class管理设备
      http://www.cnblogs.com/mylinux/p/4036589.html

  • 相关阅读:
    关于最近
    Cryptography Application Block
    关于修改SQL SERVER 登陆模式
    我与软件工程
    JAVA之路(一)
    C#基础:类的继承与多态
    简述java语言的特点
    简述java虚拟机的工作原理
    阿里云盾特点及防DDoS攻击服务介绍
    智能DNS
  • 原文地址:https://www.cnblogs.com/mylinux/p/4039829.html
Copyright © 2011-2022 走看看