嵌入式LINUX驱动学习之11 物理地址-用户空间虚拟地址映射mmap()
一、头文件、函数及说明
/* 物理地址到用户空间虚拟地址的映射函数:remap_pfn_range()
头文件位置:include/linux/mm.h
*/
int remap_pfn_range(struct vm_area_struct *u_vma, unsigned long addr,\
unsigned long pfn, unsigned long size, pgprot_t);
/*
参数说明:
u_vma : 用户空间虚拟内存空间,
addr : 要映射的目的虚拟内存空间, 即:u_vma -> vm_start
pfn : 物理地址,单位为页,即:物理地址>>12
size : 要映射的大小
prot : 权限 即:u_vma -> vm_page_prot
*/
//用户空间mmap()函数调用的底层函数:
int (*mmap) (struct file *, struct vm_area_struct *);//include/linux/fs.h
// 结构体:struct vm_area_struct{}
struct vm_area_struct {
struct mm_struct * vm_mm; //用户虚拟内存空间
unsigned long vm_start; //用户虚拟内存空间,进程操作文件的开始地址
unsigned long vm_end; //用户虚拟内存空间,进程操作文件的结束地址
struct vm_area_struct *vm_next, *vm_prev;//由内核维护的双向链表
pgprot_t vm_page_prot; //权限
//............省略更多内容,可参见:include/linux/mm_types.h..............
}
二、代码举例(内核空间)
功能:
实现将硬件的物理地址和用户空间的虚拟地址映射,这样就可以在用户空间操作硬件设备
include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
unsigned long led_phy = (0xC001C000 >> 12); //定义全局无符号长整形数据保存物理地址
struct vm_area_struct *g_led_vma;//用于指向用户虚拟内存空间
static int mmap_led (struct file * file , struct vm_area_struct * u_led_vma){
g_led_vma = u_led_vma;
g_led_vma -> vm_page_prot = pgprot_noncached(g_led_vma -> vm_page_prot);//关闭缓存功能
remap_pfn_range(g_led_vma,g_led_vma -> vm_start,\
led_phy,4096,g_led_vma ->vm_page_prot);
return 0;
}
struct file_operations f_ops = {
.owner = THIS_MODULE,
.mmap = mmap_led
};
struct miscdevice misc_fops = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mmap_led",
.fops = &f_ops
};
static int led_init(void){
misc_register(&misc_fops);
return 0;
}
static void led_exit(void){
misc_deregister(&misc_fops);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
三、代码举例(用户空间)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#define LED_ON 0X1000
#define LED_OFF 0X1001
static void * mmf_addr;//记录分配的虚拟内存空间
static unsigned long * led_base;//保存gpio_output地址,即首地址,选择高低电平
static unsigned long * led_outenb;//保存gpio_outenb地址,选择输入输出功能
static unsigned long * led_altfn;//保存gpio_altfn地址,功能复用器
static void led_run(unsigned int arg){
if(arg == LED_ON)
*led_base &= ~(0x1 << 7);//开灯
else
*led_base |= (0x1 << 7);//关灯
}
int main(int argc , char * argv[]){
int fp;
int ucmd;
void * mmf_addr;
if(argc != 3)
goto comm_err;
fp = open(argv[1],O_RDWR);
if(fp < 0)
goto file_err;
mmf_addr = mmap(0,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fp,0);
if(mmf_addr == MAP_FAILED)//判断分配地址空间是否成功
goto mm_err;
/*寄存器相关操作*/
led_base = (unsigned long *) mmf_addr;
led_outenb = (unsigned long *) (mmf_addr +0x04);
led_altfn = (unsigned long *) (mmf_addr + 0x20);
*led_outenb |= (0x1 << 7);
*led_base |= (0x1 << 7);
*led_altfn &= ~(0x3 << 14);
*led_altfn |= (0x1 << 14);
/*根据命令控制开关灯*/
if(!strcmp(argv[2],"on"))
ucmd = LED_ON;
else if(!strcmp(argv[2],"off"))
ucmd = LED_OFF;
else {
munmap(mmf_addr,4096);
goto comm_err;
}
led_run(ucmd);
munmap(mmf_addr,4096);//取消映射,注:取消映射后,寄存器的状态不会发生变量
return 0;
comm_err :
printf("命令错误!\n");
printf(" comm <cdev_file> <on| off> <led_num> \n");
return -1;
file_err :
printf("文件打开失败\n");
return -1;
mm_err :
printf("mmap映射失败\n");
return -1;
}