使用mmap映射IO地址给应用层使用
内核驱动mmap
在写这个驱动之前先说一个概念,在申请内存的时候,如果不主动传入不带cache的内存申请一般都是带cache,对于嵌入式SOC中的某个模块的寄存器IO映射区域,如果申请了带cache的内存,在应用层使用mmap映射得到的虚拟地址,当我们直接读写的时候,会先从pagecache中读取数据,而不是直接读取寄存器的值,这样就会存在一个不同步问题,导致应用层的写入和读出不是实时的,导致出错,所以驱动底层需要mmap映射不带cache的内存。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/slab.h> //定义kmalloc接口
#include <asm/io.h> //定义virt_to_phys接口
#include <linux/mm.h> //remap_pfn_range
#include <linux/vmalloc.h>
#include <linux/delay.h>
#define MM_SIZE 4096
static int mmap_open(struct inode *my_indoe, struct file *my_file)
{
printk("mmap_test open \n");
return 0;
}
static int mmap_release(struct inode *my_indoe, struct file *my_file)
{
printk("mmap_test release\n");
return 0;
}
static int mmap_test(struct file *myfile, struct vm_area_struct *vma)
{
vma->vm_flags |= VM_IO;//表示对设备IO空间的映射
vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP); //表示内存不回收
if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里
vma->vm_start,//虚拟空间的起始地址
vma->vm_pgoff,//与物理内存对应的页帧号,物理地址右移12位
vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍
pgprot_noncached(vma->vm_page_prot)//映射不带cache的内存
))
{
return -EAGAIN;
}
printk("In %s,pgoff = %lx, start= %lx,end = %lx\n",__func__,vma->vm_pgoff,vma->vm_start,vma->vm_end);
return 0;
}
static struct file_operations mmap_fops=
{
.open=mmap_open,
.release=mmap_release,
.mmap=mmap_test
};
static struct miscdevice misc={
.minor=255,
.name="mmap_test", // /dev/下的名称
.fops=&mmap_fops,
};
static int __init mmap_init(void)
{
/*1. 注册杂项字符设备*/
misc_register(&misc);
printk("mmap test init 驱动安装成功!\n");
return 0;
}
static void __exit mmap_exit(void)
{
/*2. 注销*/
misc_deregister(&misc);
printk("mmap test exit驱动卸载成功!\n");
}
module_init(mamp_init);
module_exit(mmap_exit);
MODULE_AUTHOR("mmap"); //声明驱动的作者
MODULE_DESCRIPTION("mmap模块测试"); //描述当前驱动功能
MODULE_LICENSE("GPL"); //驱动许可证。支持的协议GPL。
测试应用程序
比如需要访问0x02000000
开始的地址,长度为0x400,因为mmap需要以page
来映射 ,所以需要映射一个page
页的大小。
应用层的mmap函数原型介绍
mmap函数用于将一个文件或者其它对象映射进内存,通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。
头文件:<sys/mman.h>
函数原型:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
映射函数
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
addr: 指定映射的起始地址,通常设为NULL,由系统指定。
length:映射到内存的文件长度。
prot:映射的保护方式,可以是:PROT_EXEC:映射区可被执行
PROT_READ:映射区可被读取
PROT_WRITE:映射区可被写入
PROT_NONE:映射区不能存取
Flags:映射区的特性,可以是:MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。
MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy_on_write),对此区域所做的修改不会写回原文件。
fd:由open返回的文件描述符,代表要映射的文件。
offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射。
解除映射
int munmap(void *start, size_t length);
功能:取消参数start所指向的映射内存,参数length表示欲取消的内存大小。
测试应用
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <ctype.h>
#define PAGESIZE 4096
#define readl(addr) (*((volatile unsigned long *)(addr)))
#define writel(v, addr) (*((volatile unsigned long *)(addr)) = (unsigned int)(v))
static int mmap_test(unsigned int reg_value)
{
unsigned char* mmap_io;
int fd;
fd = open("/dev/mmap_test", 2);
if(fd<0)
{
printf("the drivers is error!!!\n");
return -1;
}
mmap_io =(unsigned char*)mmap(NULL, 4096,PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0x02000000);
if(mmap_io == NULL)
{
printf("projection error:%s...%d\n", __func__, __LINE__);
return -1;
}
printf("mmap_io reg(0x02000000) value 0x%x\n", readl(sid_oem));
writel(reg_value, mmap_io);
printf("mmap_io reg(0x02000000) value 0x%x\n", readl(sid_oem));
munmap(mmap_io, PAGESIZE);
close(fd);
return 0;
}
int main(int argc, char **argv)
{
int ret;
unsigned int reg_value;
if (argc < 1) {
return -1;
}
reg_value = strtol(argv[1], NULL, 16);
mmap_test(reg_value);
return 0;
}