zoukankan      html  css  js  c++  java
  • 字符设备驱动另一种写法—mmap方法操作LED

    最近在看韦老师的视频,讲解了很多种字符设备的驱动写法。经过自己的研究之后,我发现还有另外一种写法,直接在应用层操作,省去了内核中的地址映射部分,使得用户可以在应用层直接操作LED。
          
    mmap方法是把设备物理地址直接映射到用户空间的一种系统调用方法,他使得用户可以在应用层直接操作硬件设备,而不必在驱动里使用ioremap做地址映射。这在一定程度上实现了传说中的“零拷贝”技术。即,设备的数据不用经过内核的转存,再向应用层提交数据。由于设备物理地址直接映射到了用户空间,所以就相当于省去了内核的中间媒介,用户空间直接去操作硬件设备。
       
    总结一下,mmap方法的用处是把设备(文件)内容直接映射到进程虚拟空间,通过对这个虚拟地址的读写修改,实现对设备(文件)的读写和修改,从而不必使用read、write等系统调用即可实现对设备的操作。

    mmap的内核态函数为:
    int (*mmap)(struct file *filp,struct vm_area_struct *vma)
    结构体struct vm_area_struct *vma是我们在使用mmap系统调用的时候内核帮我们找到的虚拟地址区间,它的主要成员是:
        vma->vm_start:  映射后的用户态虚拟地址起始地址;
        vma->vm_end:   映射后的用户态虚拟地址结束地址;
        vma->vm_pgoff:   物理地址所在的页帧号,它的值由用户空间传进来的物理地址右移PAGE_SHIFT位得到,PAGE_SHIFT值为12,那么它右移12位就得到物理地址的页帧号(一页大小为4KB)。

    下面是我写的内核驱动程序,在TQ2440开发板上运行:

    #include linux/kernel.h

     #include linux/init.h

     #include linux/module.h

     #include linux/fs.h

     #include linux/cdev.h

     #include linux/mm.h

     #include linux/device.h

     #define DEV_NAME  mmapled

     struct mmapled

    {

       dev_t devno;

       struct cdev mcdev;

       struct class *mmap_class;

    };

    struct mmapled *mpt;

    int mmapled_open(struct inode *inode, struct file *filp)

    {

        printk(In kernel open,major =%d,minor = %d ,MAJOR(mpt->devno),MINOR(mpt->devno));

        return 0;

    }

    int mmapled_close(struct inode *inode,struct file *filp)

    {

        return 0;

    }

    /* mmap系统调用函数 */

    int mmapled_mmap(struct file *filp,structvm_area_struct *vma)

    {

       int ret;

       vma->vm_flags |= VM_RESERVED;

       vma->vm_flags |= VM_IO;

       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

     /* vma->vm_pgoff为用户层off, PAGE_SHIFT,即物理地址的页帧号,映射大小必为PAGE_SIZE整数倍 */

      ret =remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,vma->vm_end -vma->vm_start,vma->vm_page_prot);

     if(ret)

     {

        printk(remap_pfn_range err! );

        return -EAGAIN;

     }

    printk(In %s,pgoff = %lx, start= %lx,end = %lx ,__func__,vma->vm_pgoff,vma->vm_start,vma->vm_end);

    return 0;

    }

    /* 文件操作结构体 */

    struct file_operations mmapled_fops =

    {

       .owner   =THIS_MODULE,

       .open     =mmapled_open,

       .release  = mmapled_close,

       .mmap  = mmapled_mmap,

    };

    /* 驱动程序入口函数 */

    int mmapled_init(void)

    {

        int ret;

        mpt = kzalloc(sizeof(struct mmapled),GFP_KERNEL);

    if(!mpt)

     {

        printk(kzalloc mpt err! );

        return -ENOMEM;

     }

      /* 动态分配主设备号,起始次设备号为0 */

    ret = alloc_chrdev_region(mpt->devno,0,1,DEV_NAME);

    if(ret)

      {

         printk(register chrdev err! );

         kfree(mpt);

         return ret;

      }

       /* 创建类,用于自动创建设备节点 */

      mpt->mmap_class=class_create(THIS_MODULE, DEV_NAME);

      if(IS_ERR(mpt->mmap_class))

       {

          printk(KERN_ALERTabsmem_class create failed. );

          kfree(mpt);

          unregister_chrdev_region(mpt->devno,1);

          return -1;

      }

    cdev_init(mpt-mcdev, mmapled_fops);

    mpt->mcdev.owner=THIS_MODULE;

    cdev_add(mpt->mcdev,mpt->devno,1);

    /* 创建设备节点mmapled0 */

    device_create(mpt->mmap_class,NULL,mpt->devno,NULL,mmapled%d,0);

    return 0;

    }

    /* 驱动程序出口函数 */

    void mmapled_exit(void)

    {

       device_destroy(mpt->mmap_class,mpt->devno);

       cdev_del(mpt->mcdev);

       class_destroy(mpt->mmap_class);

       unregister_chrdev_region(mpt->devno,1);

       kfree(mpt);

    }

    module_init(mmapled_init);

    module_exit(mmapled_exit);

    MODULE_LICENSE(GPL);


      用户态的mmap函数接口为:
      void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
         函数参数的意义如下:
         addr:指定映射的起始地址,即进程虚拟空间的虚拟地址,人为的指定;通常设置为NULL,让内核帮我们去指定;
         len:  要映射的区间大小;
         prot: 映射区的保护方式,可以取以下值:
                  PROC_EXEC:映射区可被执行;
                  PROC_READ:映射区可被读取;
                  PROC_WRITE:映射区可写;
                  PROC_NONE:映射区不能存取。
         flags是映射区的特性,可以取以下值:
                 MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享;
                 MAP_PRIVATE:对映射区的写入会产生一个映射区的复制(COPY_ON_WRITE),对此映射区的修改不会写入源文件;
        fd:由open函数返回的文件描述符;
        offset:文件开始处的偏移量,必须是分页大小的整数倍。
        函数返回值:映射得到的用户虚拟地址;

    下面是我写的用户态的程序,供大家参考(在TQ2440开发板上运行,如果是其他开发板,可以参考原理图做一些修改):

    /* 函数功能:实现4个LED灯的同时亮灭,间隔为1秒 */

    #include stdio.h

    #include string.h

    #include fcntl.h

    #include sys/mman.h

     

    #define DEV_NAME  /dev/mmapled0

    int main()

    {

      int fd,k;

      void *start, *reg=NULL;

     fd = open(DEV_NAME,O_RDWR);

       if(fd&0)

      {

          printf(Open device err! );

          return -1;

       }

    /*参数解释:

        * NULL:映射到的内核虚拟地址,设置为NULL由内核决定

        * 1024*4:映射大小,最小一页,必为页大小的整数倍

        * 映射区的权限

        * 对映射区的修改改变映射区的内容

        * fd:open返回的文件描述符

        * 物理地址,一个页的起始物理地址,它PAGE_SHIFT之后传给驱动的vma结构体的vm_pgoff

        */

    /*0x56000000是LED等所在的GPIO口的BANK起始物理地址*/

    /*start是得到的虚拟地址*/

    start =mmap(NULL,1024*4,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x56000000);

    if(start == NULL)

     {

          printf(mmap err! );

          return -1;

     }

      

    reg = start + 0x10;                        // GPBCON,控制寄存器

    *(unsigned*)reg = 0xfffc03ff;        // [17:10]清零

    *(unsigned*)reg |= 0x00015400;  // [17:10]=01010101,输出功能

    reg = start + 0x14;                       // GPBDAT

    /* 量灭k次,实现对LED的操作 */

     k=25;

     while(k--)

     {

          *(unsigned*)reg & = ~(0x1e0); // [8:5], set 0,led on

          sleep(1);

          *(unsigned*)reg |= 0x1e0;    // [8:5], set 1,led off

          sleep(1);

          printf(k= %d ,k);

     }

    /* 取消映射 */

    munmap(start,1024*4);

    close(fd);

    return 0;

    }

    完。

    稍微修改代码即可在jz2440上运行,需要代码的学员请留下邮箱,我们发给您。

  • 相关阅读:
    第六篇:python高级之网络编程
    第五篇:python高级之面向对象高级
    sublime插件开发教程
    Metatable In Lua 浅尝辄止
    cocos2dx-lua绑定之代码编辑器
    sublime入门文章
    Sublime Text快捷键
    lua中文教程【高级知识】
    lua基本语法
    Lua 不是 C++
  • 原文地址:https://www.cnblogs.com/weidongshan/p/8178937.html
Copyright © 2011-2022 走看看