[经验] 字符设备驱动另一种写法—mmap方法操作LED

[复制链接]

版主

发表于 2018-1-2 17:38:43   808 查看 1 回复 显示全部楼层 倒序浏览
分享
本帖最后由 weidongshan 于 2018-1-2 17:41 编辑

来源:100ask论坛
首发平台:威信订阅号baiwenkeji

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

mmap的内核态函数为:
  1. 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开发板上运行:
#includelinux/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;
};
structmmapled *mpt;
intmmapled_open(struct inode *inode, struct file *filp)
{
    printk(In kernel open,major =%d,minor =%d\n,MAJOR(mpt->devno),MINOR(mpt->devno));
    return 0;
}
intmmapled_close(struct inode *inode,struct file *filp)
{
    return 0;
}
/*mmap系统调用函数 */
intmmapled_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!\n);
    return -EAGAIN;
}
printk(In%s,pgoff = %lx, start= %lx,end = %lx\n,__func__,vma->vm_pgoff,vma->vm_start,vma->vm_end);
return0;
}
/* 文件操作结构体 */
structfile_operations mmapled_fops =
{
   .owner  =THIS_MODULE,
   .open    =mmapled_open,
   .release = mmapled_close,
   .mmap = mmapled_mmap,
};
/* 驱动程序入口函数 */
intmmapled_init(void)
{
    int ret;
    mpt = kzalloc(sizeof(structmmapled),GFP_KERNEL);
if(!mpt)
{
    printk(kzalloc mpt err!\n);
    return -ENOMEM;
}
  /* 动态分配主设备号,起始次设备号为0 */
ret= alloc_chrdev_region(mpt->devno,0,1,DEV_NAME);
if(ret)
  {
     printk(register chrdev err!\n);
     kfree(mpt);
     return ret;
  }
   /* 创建类,用于自动创建设备节点 */
  mpt->mmap_class=class_create(THIS_MODULE,DEV_NAME);
  if(IS_ERR(mpt->mmap_class))
   {
      printk(KERN_ALERTabsmem_class createfailed.\n);
      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);
return0;
}
/* 驱动程序出口函数 */
voidmmapled_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函数接口为:
  1. void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_toffset)
复制代码

   函数参数的意义如下:
    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秒 */
#includestdio.h
#includestring.h
#includefcntl.h
#includesys/mman.h
  
#defineDEV_NAME  /dev/mmapled0
intmain()
{
  int fd,k;
  void *start, *reg=NULL;
fd = open(DEV_NAME,O_RDWR);
   if(fd&0)
  {
      printf(Open device err!\n);
      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!\n);
      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\n,k);
}
/* 取消映射 */
munmap(start,1024*4);
close(fd);
return0;
}
完。
稍微修改代码即可在jz2440上运行,需要代码的学员请留下邮箱,我们发给您。
标签:linux驱动

版主

发表于 2018-1-3 12:34:29  
好文,不错的资料分享,谢了
回复

点赞 举报

高级模式
您需要登录后才可以回帖 登录 | 注册

关闭

站长推荐 上一条 /7 下一条

快速回复 返回顶部 返回列表
-

推荐专区

技术干货集中营

专家问答

用户帮助┃咨询与建议┃版主议事

工程师杂谈

工程师创意

工程师职场

论坛电子赛事

社区活动专版

发烧友活动

-

嵌入式论坛

ARM技术论坛

Android论坛

Linux论坛

单片机/MCU论坛

FPGA|CPLD|ASIC论坛

DSP论坛

嵌入式系统论坛

-

电源技术论坛

电源技术论坛

无线充电技术

-

硬件设计论坛

PCB设计论坛

电路设计论坛

电子元器件论坛

控制|传感

总线技术|接口技术

-

测试测量论坛

LabVIEW论坛

Matlab论坛

测试测量技术专区

仪器仪表技术专区

-

EDA设计论坛

multisim论坛

PADS技术论坛

Protel|AD|DXP论坛

Allegro论坛

proteus论坛|仿真论坛

EasyEDA-中国人自已的EDA工具

Orcad论坛

-

综合技术与应用

电机控制

智能电网

光电及显示

参考设计中心

汽车电子技术论坛

医疗电子论坛

-

开源硬件

-

无线通信论坛

无线通信技术专区

天线|RF射频|微波|雷达技术

-

IC设计论坛

芯片测试与失效分析

Mixed Signal/SOC[数模混合芯片设计]

Analog/RF IC设计

设计与制造封装测试

-

厂商专区

TI论坛

TI Deyisupport社区

-

检测技术与质量

电磁兼容(EMC)设计与整改

安规知识论坛

检测与认证

-

消费电子论坛

手机技术论坛

平板电脑/mid论坛

音视/视频/机顶盒论坛

-

电子论坛综合区

聚丰众筹官方社区

新人报道区

聚丰供应链

-

论坛服务区

-

供求信息发布

供需广告

招聘┃求职发布区

电子展览展会专区