背景介绍:作为嵌入式开发人员,经常会遇到需要配置IO地址空间的寄存器,然而一般情况下,这些配置会由芯片厂商事先在kernel中(通常是在BSP代码中)配置好,所以即使开发人员知道配那个、配多少,也必须先钻到kernel代码中分析后修改。那么,其实确实有一种方法,允许应用程序在用户空间直接读写IO设备,避免了修改内核或驱动,提升了效率。
简介:
Linux内核为用户提供了一个/dev/mem的驱动程序,使应用程序在用户空间直接访问物理地址空间(包含IO地址空间)成为可能。/dev/mem是linux下的一个字符设备,源文件是kernel/drivers/char/mem.c,这个设备文件是专门用来读写物理地址用的。
网络上有个对/dev/mem很贴切的评价:/dev/mem是个好玩的东西,你竟然可以直接访问物理内存,这在linux下简直太神奇了,就想一个小偷想偷银行,可是发现银行戒备森严,正在小偷苦无对策的时候,突然发现银行有个后门,而且这个后门直通银行的金库。
既然/dev/mem这么强大,难道就不怕黑客利用/dev/mem对内存或IO外设做非法操作吗?其实内核有对/dev/mem访问的物理地址空间做检查,而且通过CONFIG_STRICT_DEVMEM来控制。如果关闭选项CONFIG_STRICT_DEVMEM,用户就可以通过/dev/mem设备访问所有物理地址空间。(注:如果没有关闭CONFIG_STRICT_DEVMEM选项,在执行如上应用程序时可能会出现Segmentation fault的错误。)
代码:
代码实现很简单,通过mmap将物理地址(/dev/mem就是物理地址空间的全镜像)映射到用户空间的虚拟地址上,然后就可以读写这些物理地址空间。如下是我参考:
http://blog.chinaunix.net/uid-20680966-id-5520530.html,读写gpio pin的应用程序。
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <sys/mman.h>
-
-
#define REGISTER_BYTE_LEN 0x4 //寄存器长度
-
#define UINT_BYTE_LEN 0x4 //unsigned int型占用的字节长度
-
-
int main (int argc, char* argv[])
-
{
-
if (5 > argc)
-
{
-
printf("Usage:\n%s [register base address] [register offset address] [mask] [value]\n", argv[0]);
-
return -1;
-
}
-
-
unsigned int addr_base = strtoul( argv[1], 0, 16);
-
unsigned int addr_offset = strtoul( argv[2], 0, 16);
-
unsigned int mask = strtoul( argv[3], 0, 16);
-
unsigned int value = strtoul( argv[4], 0, 16);
-
unsigned int reg_value = 0;
-
-
int mem_fd = open( "/dev/mem", O_RDWR|O_SYNC );
-
if (mem_fd == -1)
-
{
-
perror("open /dev/mem");
-
return -1;
-
}
-
-
unsigned int size = addr_offset + REGISTER_BYTE_LEN;
-
unsigned int *addr = (unsigned int*)mmap( 0, size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, mem_fd, addr_base);//最后参数必须与页大小对齐
-
close(mem_fd);
-
if (addr == MAP_FAILED)
-
{
-
perror("mmap");
-
return -1;
-
}
-
-
//read
-
printf( "0x%08x\n", *(addr+ (size-REGISTER_BYTE_LEN)/UINT_BYTE_LEN) );
-
//write
-
reg_value = ((*(addr+ (size-REGISTER_BYTE_LEN)/UINT_BYTE_LEN) & ~mask) | value);
-
*(volatile unsigned int *)(addr+ (size-REGISTER_BYTE_LEN)/UINT_BYTE_LEN) = reg_value;
-
printf( "0x%08x\n", *(addr+ (size-REGISTER_BYTE_LEN)/UINT_BYTE_LEN) );
-
-
munmap(addr, size);
-
-
return 0;
-
}
调试:
我用此程序,通过gpio来控制ICETEK OMAPL138上的4个面板灯。这里分3步:先配置LED所在pin的pinmux为gpio;再配置gpio的方向;最后控制gpio拉高拉低。
需要注意的是,LED所在pin的pinmux不能使用此应用程序在用户空间配置,原因芯片手册也有写:
在内核设置好pinmux后(怎么配见我以前的文章),接下来就是设置gpio方向和拉高拉低了,串口控制脚本如下:
./gpioctl 0x01e26000 0x038 0x00f00000 0x00000000//设方向
./gpioctl 0x01e26000 0x03c 0x00f00000 0x00000000//拉低4个灯---点亮
./gpioctl 0x01e26000 0x03c 0x00f00000 0x00f00000//拉高4个灯---熄灭
阅读(2801) | 评论(0) | 转发(0) |