Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1713844
  • 博文数量: 143
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1462
  • 用 户 组: 普通用户
  • 注册时间: 2016-08-23 11:14
文章分类

全部博文(143)

文章存档

2022年(3)

2021年(13)

2020年(21)

2019年(8)

2018年(28)

2017年(7)

2016年(63)

我的朋友

分类: 嵌入式

2016-09-06 19:32:02

背景介绍:作为嵌入式开发人员,经常会遇到需要配置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的应用程序。

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>
  7. #include <sys/mman.h>

  8. #define REGISTER_BYTE_LEN 0x4    //寄存器长度
  9. #define UINT_BYTE_LEN 0x4    //unsigned int型占用的字节长度

  10. int main (int argc, char* argv[])
  11. {
  12.     if (5 > argc)
  13.     {
  14.         printf("Usage:\n%s [register base address] [register offset address] [mask] [value]\n", argv[0]);
  15.         return -1;
  16.     }

  17.     unsigned int addr_base = strtoul( argv[1], 0, 16);
  18.     unsigned int addr_offset = strtoul( argv[2], 0, 16);
  19.     unsigned int mask = strtoul( argv[3], 0, 16);
  20.     unsigned int value = strtoul( argv[4], 0, 16);
  21.     unsigned int reg_value = 0;

  22.     int mem_fd = open( "/dev/mem", O_RDWR|O_SYNC );
  23.     if (mem_fd == -1)
  24.     {
  25.         perror("open /dev/mem");
  26.         return -1;
  27.     }
  28.     
  29.     unsigned int size = addr_offset + REGISTER_BYTE_LEN;
  30.     unsigned int *addr = (unsigned int*)mmap( 0, size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, mem_fd, addr_base);//最后参数必须与页大小对齐
  31.     close(mem_fd);
  32.     if (addr == MAP_FAILED)
  33.     {
  34.         perror("mmap");
  35.         return -1;
  36.     }
  37.     
  38.     //read
  39.     printf( "0x%08x\n", *(addr+ (size-REGISTER_BYTE_LEN)/UINT_BYTE_LEN) );
  40.     //write
  41.     reg_value = ((*(addr+ (size-REGISTER_BYTE_LEN)/UINT_BYTE_LEN) & ~mask) | value);
  42.     *(volatile unsigned int *)(addr+ (size-REGISTER_BYTE_LEN)/UINT_BYTE_LEN) = reg_value;
  43.     printf( "0x%08x\n", *(addr+ (size-REGISTER_BYTE_LEN)/UINT_BYTE_LEN) );
  44.     
  45.     munmap(addr, size);

  46.     return 0;
  47. }
调试
  我用此程序,通过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个灯---熄灭

阅读(2715) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~