x86体系和ARM体系的寻址方式是有差别的:
在x86下,为了能够满足CPU高速地运行,内存与CPU之间通过北桥相连并通过地址方式访问,而外设通过南桥与CPU相连并通过端口访问。
在ARM下也实现了类似的操作,通过两条不同的总线(AHBBUS和APBBUS)来连接不同访问速度的外设。但是它与x86不同,无论是内存还是外设,ARM都是通过地址访问。
因为这两种访问方式的不同,linux分出了两种不同的访问操作:
以地址方式访问硬件——使用IO内存操作。
以端口方式访问硬件——使用IO端口操作。
在ARM下,访问寄存器就像访问内存一样——从指定的寄存器地址获取数据,修改。所以,ARM下一般是使用IO内存的操作。但这并不是说IO端口的操作在ARM下不能用,它们的代码差不多,只是没有使用的必要,下面也将介绍IO内存操作。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、如何使用IO内存获得硬件的地址
之前已经说过,不能在linux使用实际的物理地址,要对指定的物理地址进行操作,必须要先将物理地址与虚拟地址对应,通过虚拟地址访问。于是有了以下的物理地址映射函数:
#include
void*ioremap(unsigned long phys_addr, unsigned long size);
其实这也是上一节介绍的内存分配的一种方式,它同样会建立新页表来管理虚拟地址。函数传入两个参数,需要访问的物理内存(寄存器)的首地址phys_addr和这段内存区域的大小size,返回与该段物理地址对应的虚拟地址。这段地址可以多次被映射,当然,每次映射的虚拟地址也不一样。
对应的也有撤销映射关系的函数:
voidioumap(void *addr);
接下来,我将会从一个裸板的ARMled驱动开始,讲解linux下的操作和裸板有什么不一样。
我的ARM裸板程序是在linux下编写的,我不知道这跟win下使用ADS有什么区别,在裸板驱动中,一般我是通过这样的办法来操作寄存器的:
首先,先给个地址定义个容易记的名字:
#defineGPECON *(volatile unsigned long *) 0x56000040
接着,我就要操作这个GPECON寄存器了:
*GPECON&= ~(3 << 24); //将24和25位清零
*GPECON|= (1 << 24); //将24和25位分别赋值为1、0
可以看到,操作寄存器其实就是拿个地址出来进行操作。其实在linux下也是一样,只是操作的时候不能使用物理地址,需要用映射出来的虚拟地址。
上个函数,这个程序我将要点亮连在我开发板上的led灯,这个灯接在我开发板的GPE12上,如果需要下载程序运行,需要改一下接口。
/*5th_mm_2/1st/test.c*/
1#include
2#include
3
4#include //上面介绍的函数需要包含该头文件
5
6volatile unsigned long virt, phys;//用于存放虚拟地址和物理地址
7volatile unsigned long *GPECON, *GPEDAT, *GPEUP;//用与存放三个寄存器的地址
8
9void led_device_init(void)
10{
11 phys = 0x56000000;//1、指定物理地址
12 virt = (unsigned long)ioremap(phys,0x0c);//2、通过ioremap获得对应的虚拟地址
13//0x0c表示只要12字节的大小
14 GPECON = (unsigned long *)(virt + 0x40);//3