首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

ok6410学习笔记(8.mmap地址映射之led驱动)

ok6410学习笔记(8.mmap地址映射之led驱动)

  前两节说mmap可以映射到真实的物理地址上面去,所有就打算把ok6410的#define GPMCON (*(volatile unsigned int *)0x7f008820)和#define GPMDAT (*(volatile unsigned int *)0x7f008824)两个寄存器地址映射上去,然后实现对led的控制。细节就不说了,直接上代码。
       1.这个驱动弄了两天,有一个问题一定要注意到就是(unsigned long)(0x7f008000)>>PAGE_SHIFT  这个求页桢的东西 会把后12位全都清零,导致地址写错,比如GPMCON地址是0x7f008820,对于这个就要加0x820,因为后12位清零了,千万注意!!!!

2.还有一个常见的问题就是size=vma->vm_end-vma->vm_start这个值,如果在mmap的申请中的大小是小于1024*4的,size就是1024*4,如果申请的是大于4*1024的分配的就是8*1024。

      3.第三问题就是系统调用mmap返回的void*地址,应该是在内存中已经分配好了虚拟地址的,不需要再malloc的,mmap中有地址分配的过程。
本节代码:memdev.c
[cpp] view plaincopy


  • /**************************************************************************
  • 文件名:            memdev.c
  • 日期:              2013/06/03   
  • 头文件:            memdev.h
  • 功能:              简单字符驱动(开辟一块内存当做字符驱动进行读写) 此为驱动部分
  • 环境:              Redhat企业版5  内核版本2.6.18-53.el5  
  • 作者:              Hao  
  • 流程:              1.分配设备号(a.静态申请  b.动态分配)
  •                                      2.创建设备文件(a.手动创建mknod(需要设备号)注“设备名和设备文件名”  b.自动创建)
  •                                      3.设备注册(a.设备注册分配  b.设备注册初始化  c.设备注册添加)
  •                                      4.实现file_operation中的函数
  •                                      5.设备注销(cdev_del)
  •                                      6.设备号注销
  • ***************************************************************************/
  • #include <linux/module.h>
  • #include <linux/types.h>
  • #include <linux/fs.h>
  • #include <linux/errno.h>
  • #include <linux/mm.h>
  • #include <linux/sched.h>
  • #include <linux/init.h>
  • #include <linux/cdev.h>
  • #include <linux/slab.h>
  • #include <linux/poll.h>
  • #include <linux/device.h>
  • #include <asm/io.h>
  • #include <asm/system.h>
  • #include <asm/uaccess.h>
  • #include <asm/atomic.h>
  • #include "memdev.h"
  • MODULE_AUTHOR("Hao");  
  • MODULE_LICENSE("GPL");  

  • static
    int mem_major=MEMDEV_MAJOR; //定义主设备号(定义一个全局变量在read write memdev_exit中都能用 )
  • module_param(mem_major, int, S_IRUGO);//接收模块参数主设备号
  • struct mem_dev *mem_devp; //定义mem设备描述结构体指针
  • struct cdev c_dev;         //设备注册分配 替换了struct cdev *cdev_alloc() 全局变量都可以使用

  • struct
    class *mem_class;  /*设备的类*/

  • /**************************************************************************
  • 函数名:                     mem_open
  • 函数功能:                   文件打开函数 使private_data指向 字符驱动模块
  • 函数参数:                   inode存储文件物理信息的结构(包含设备号)   file结构
  • 函数返回值:                 返回0为正常执行   
  • ***************************************************************************/
  • 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; /*不是很了解private_data   
  •     宋宝华的linux设备驱动开发详解,93页写到私有数据指针private_data在设备驱动中背广泛使用,大多数指向设备驱动自定义用于描述设备的结构体。*/
  •     return 0;   
  • }  
  • /**************************************************************************
  • 函数名:                     mem_release
  • 函数功能:                   文件关闭函数  
  • 函数参数:                   inode存储文件物理信息的结构(包含设备号)   file结构
  • 函数返回值:                 返回0为正常执行  
  • ***************************************************************************/
  • int mem_release(struct inode *inode, struct file *filp)  
  • {  
  •     return 0;  
  • }  
  • /**************************************************************************
  • 函数名:                     mem_read
  • 函数功能:                   文件读取函数  
  • 函数参数:                   struct file *filp(open的时候系统产生的结构,根据inode来的)
  •                   char __user *buf  存储读取数据的空间
  •                   size_t size   读取的大小  size_t应该就是typedef  unsigned int的类型
  •                   loff_t *ppos   当前文件指针的位置   
  • 函数返回值:                 返回ret  正常应该是size的值  
  • ***************************************************************************/
  • static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)  
  • {  
  •     unsigned long p = *ppos; //保存下文件指针的当前位置
  •     unsigned int count=size;   
  •     int ret = 0;  
  •         struct mem_dev *dev = filp->private_data; //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
  •         {  
  •             *ppos += count;  
  •             ret = count;  
  •             printk(KERN_EMERG "read %d bytes(s) from %ld\n", count, p);  
  •         }  
  •     return ret;  
  • }  
  • /**************************************************************************
  • 函数名:                     mem_write
  • 函数功能:                   文件写函数  
  • 函数参数:                   参数同read函数
  •                   注意:struct file *filp,char __user *buf和size_t size是内核从应用层api-fread中传递下来的
  •                   具体是怎么传递的不详  看内核代码 Read_write.c
  • 函数返回值:                 返回size为正常执行  
  • ***************************************************************************/
  • static ssize_t mem_write(struct file *filp, const
    char __user *buf, size_t size, loff_t *ppos)  
  • {  
  •     unsigned long p =  *ppos;  
  •     unsigned int count = size;  
  •     int ret = 0;  
  •     struct mem_dev *dev = filp->private_data;  
  •     if (p >= MEMDEV_SIZE)//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 {  
  •         *ppos += count;// 不知道这里用filp->f_pos+=count行不行  因为我觉得filp->f_pos和ppos是一个地址
  •         ret = count;  

  •         printk(KERN_EMERG "written %d bytes(s) from %ld\n", count, p);  
  •     }  
  •     return ret;  
  • }  
  • /**************************************************************************
  • 函数名:                     mem_llseek
  • 函数功能:                   文件位置指针定位函数  
  • 函数参数:                   struct file *filp
  •                   loff_t offset偏移量
  •                   int whence 三种可能SEEK_SET   SEEK_CUR  SEEK_END
  • 函数返回值:                 返回0为正常执行  
  • ***************************************************************************/
  • static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)  
  • {   
  •     loff_t newpos;  //定义中间变量
  •     switch(whence) {  
  •         case 0: /* SEEK_SET */
  •             newpos = offset;  
  •             break;  
  •         case 1: /* SEEK_CUR */
  •             newpos = filp->f_pos + offset;  
  •             break;  
  •         case 2: /* SEEK_END */
  •             newpos = MEMDEV_SIZE -1 + offset;  
  •             break;  
  •         default: /* can't happen */
  •             return -EINVAL;  
  •     }  
  •     if ((newpos<0) || (newpos>MEMDEV_SIZE))  //判断newpos是否有效
  •         return -EINVAL;  
  •     filp->f_pos = newpos;  //赋值 定位
  •     return newpos;  
  • }  

  • /**************************************************************************
  • 函数名:                     mem_map
  • 函数功能:                   映射函数   将物理地址和虚拟地址建立页表  
  • 函数参数:                    
  • 函数返回值:                 返回0为正常执行  
  • ***************************************************************************/
  • static
    int mem_mmap(struct file *filp, struct vm_area_struct *vma)  
  • {  
  •         struct mem_dev *dev = filp->private_data;  
  •         vma->vm_flags|=VM_IO;  
  •         vma->vm_flags|=VM_RESERVED;  
  •         if(remap_pfn_range(vma, vma->vm_start,(unsigned long)(0x7f008000)>>PAGE_SHIFT,vma->vm_end-vma->vm_start, vma->vm_page_prot))  
  •                     return -EAGAIN;  
  •                         printk(KERN_EMERG "NOW is in kernel mmap!!!\n");  
  •                         printk(KERN_INFO "[size = %ld]\n", (unsigned long)(vma->vm_end-vma->vm_start));  
  •         return 0;     
  • }  


  • static
    const
    struct file_operations mem_fops =  //定义此字符设备的file_operations
  • {                       //这里是对结构体整体赋值的方式
  •     .owner = THIS_MODULE,  
  •     .llseek = mem_llseek,  //函数名都可以自己定义  都是函数指针
  •     .mmap = mem_mmap,  
  •     .read = mem_read,  
  •     .write = mem_write,  
  •     .open = mem_open,  
  •     .release = mem_release,  
  • };  

  • /**************************************************************************
  • 函数名:                     memdev_init
  • 函数功能:                   内核模块入口函数
  • 函数参数:                   无
  • 函数返回值:                 返回0为正常执行   返回result为设备号获取不成功
  •                   返回- ENOMEM为内存分配不成功
  • ***************************************************************************/
  • static
    int memdev_init()  
  • {  
  •     int i;  
  •     int result;  
  •     dev_t dev_num=MKDEV(mem_major,0);//将主设备号和次设备号转换成32位的设备号给   静态申请设备号用的
  •     if(mem_major)  
  •         result=register_chrdev_region(dev_num,2,"newmemdev");//静态申请设备号为dev_num 2个设备  设备名为“newmemdev”
  •     else
  •     {  
  •         result=alloc_chrdev_region(&dev_num,0,2,"newmemdev");//动态分配设备号   设备名可以在/proc/devices文件中找   与/dev路径找文件不同  因为一个设备文件  一个次设备号
  •         mem_major = MAJOR(dev_num);//获得主设备号
  •     }  
  •         if (result < 0)  
  •         {  
  •               return result;   //判断动态分配是否成功  不成功则退出函数
  •             }  
  •         /*设备注册 分配已经在前面完成了 为全局变量*/
  •         cdev_init(&c_dev,&mem_fops);//设备注册初始化  将设备号dev_num和file_operation建立联系
  •         c_dev.owner = THIS_MODULE;//*****************貌似可以屏蔽吧*********************
  •         cdev_add(&c_dev,dev_num,2);//设备注册  添加   设备号为dev_num 设备数为2个
  •         mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);//开辟2个字符设备模块大小的空间
  •         if (!mem_devp)    /*判断是否分配内存成功*/  
  •         {  
  •             result =  - ENOMEM;  
  •             goto fail_malloc;  
  •         }  
  •         memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev)); //将初始化2个字符设备模块大小的空间清零

  •          mem_class = class_create(THIS_MODULE, "mem_class_name");//创建设备类
  •          device_create(mem_class, NULL, MKDEV(mem_major, 0), NULL, "memdev0"); //创建设备文件 文件名为memdev0
  •         for (i=0; i < MEMDEV_NR_DEVS; i++) //对字符设备模块结构赋值
  •         {  
  •             mem_devp.size = MEMDEV_SIZE;  
  •             mem_devp.data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);//因为data是指针所以继续对其开辟4k空间了
  •             memset(mem_devp.data, 0, MEMDEV_SIZE);  
  •         }  
  •     return 0;  

  • fail_malloc:   //如果内存分配不成功直接注销设备号  
  •     unregister_chrdev_region(dev_num, 1);/*但是不明白的是 1.为什么只注销一个次设备的设备号  2.为什么不先注销cdev_dev*/
  •     return result;//虽然这里不懂 但不深究了 因为很少出现内存分配不成功的情况(在学习练习的时候)
  • }  
  • /**************************************************************************
  • 函数名:                     memdev_exit
  • 函数功能:                   内核模块退出函数
  • 函数参数:                   无
  • 函数返回值:                 无
  • ***************************************************************************/
  • static
    void memdev_exit(void)  
  • {  
  •     cdev_del(&c_dev);//设备注销
  •     kfree(mem_devp);//释放开辟的内存空间
  •     /*这里貌似没有释放mem_devp.data的指针  是不是应该kfree(mem_devp[1].data)*/
  •     /*这里有一个问题就是那两个内存分配 为什么要分配两次 为什么释放一次 我试过貌似第二次不分配也可以*/
  •     device_destroy(mem_class, MKDEV(mem_major, 0));/*注销设备*/
  •     class_destroy(mem_class);  /*删除类*/
  •     unregister_chrdev_region(MKDEV(mem_major, 0), 2);//设备号注销
  • }  

  • module_init(memdev_init);  
  • module_exit(memdev_exit);  


memdev.h

[cpp] view plaincopy


  • /**************************************************************************
  • 文件名:            memdev.c
  • 日期:              2013/05/12   17:21
  • 头文件:            memdev.h
  • 功能:              简单字符驱动(开辟一块内存当做字符驱动进行读写) 此为驱动部分
  • 环境:              Redhat企业版5  内核版本2.6.18-53.el5  
  • 作者:              Hao  
  • 流程:              1.分配设备号(a.静态申请  b.动态分配)
  •              2.创建设备文件(a.手动创建mknod(需要设备号)注“设备名和设备文件名”  b.自动创建)
  •              3.设备注册(a.设备注册分配  b.设备注册初始化  c.设备注册添加)
  •              4.实现file_operation中的函数
  •              5.设备注销(cdev_del)
  •              6.设备号注销
  • ***************************************************************************/
  • #ifndef _MEMDEV_H_
  • #define _MEMDEV_H_

  • #ifndef MEMDEV_MAJOR
  • #define MEMDEV_MAJOR 251   /*预设的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;         
  • };  

  • #endif /* _MEMDEV_H_ */


appmem.c

[cpp] view plaincopy


  • #include <stdio.h>
  • #include <stdlib.h>
  • #include <string.h>
  • #include <unistd.h>
  • #include <sys/types.h>
  • #include <sys/stat.h>
  • #include <sys/mman.h>
  • #include <fcntl.h>

  • #define uint unsigned int
  • #define uchar unsigned char
  • void delay(volatile unsigned int time)  
  • {  
  •     volatile unsigned int x,y;  
  •     for(x=0;x<2000;x++)  
  •         for(y=0;y<time;y++);  
  • }  

  • int main()  
  • {  
  •             int fd;  
  •             int i;  
  •             volatile unsigned char *map;  
  •             volatile unsigned int *GPMCON;  
  •             volatile unsigned int *GPMDAT;  
  •             volatile unsigned int *GPMPUD;  
  •             char buf[100];  
  •             if(-1==(fd=open ("/dev/memdev0", O_RDWR)))  
  •                      {  
  •                     printf("open dev0 error\n");  
  •                     _exit(EXIT_FAILURE);  
  •                 }  
  •         <span style="white-space:pre">      </span>map = (volatile unsigned char*)mmap(NULL,1024*5, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);  
  •              if(map == NULL)  
  •                 {  
  •                 printf("mmap err!\n");  
  •                 return 0;  
  •             }     

  •             GPMCON=(volatile unsigned int*)(map+0x820);  
  •             GPMDAT=(volatile unsigned int*)(map+0x824);  
  •             GPMPUD=(volatile unsigned int*)(map+0x828);  
  •         <span style="white-space:pre">      </span>*GPMCON&=~0xffff;  
  •             *GPMCON|=0x1|(0x1<<4)|(0x1<<8)|(0x1<<12);  
  •             *GPMDAT|=0xf;   
  •             printf("init is finishing!\n");  
  •         <span style="white-space:pre">  </span>while(1)  
  •             {  
  •                 for(i=0;i<4;i++)  
  •                 {  
  •                     *GPMDAT=~(1<<i);  
  •                     delay(1500);  
  •                 }  
  •             }  
  •       munmap((char*)map,1024*5);                 
  •         close(fd);  
  • }  


makefile

[plain] view plaincopy


  • ifneq ($(KERNELRELEASE),)  

  • obj-m := memdev.o  

  • else  

  • KDIR := /guoqian/linux/linux-2.6.36  
  • all:  
  •     make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-  
  • clean:  
  •     rm -f *.ko *.o *.mod.o *.mod.c *.symvers  modul*  

  • endif  


makefile x86  记录下这个文件

[plain] view plaincopy


  • ifneq ($(KERNELRELEASE),)  
  • obj-m := memdev.o  
  • else  
  • KDIR :=/lib/modules/2.6.18-53.el5/build  
  • all:  
  •     make -C $(KDIR) M=$(PWD) modules  
  • clean:  
  •     rm -f *.ko *.o *.mod.o *.mod.c *.symvers  
  • endif  


返回列表