5、映射的思考
学习了 GPIO_newbule 之后,一血关于映射上面的问题总结。
1、映射方法
实现映射的方法是通过:ioremap ,IO_ADDRESS 这两种方法,实现物理和虚拟地址的映射,它们返回的结果就是虚拟地址了,但是这两种方法的区别是:
ioremap : 动态映射,一般是在外围的控制器的地址。当 映射的时间,是当加载相对应函数的是,才完成映射的操作。
IO_ADDRESS : 静态映射,一般是寄存器资源映射上,映射的时间是,当系统启动的时候,就完成映射的操作,
1.1、IO_ADDRESS 的映射
物理地址 = 基址 + 偏移地址,所以使用 IO_ADDRESS 进行地址映射的时候,存在两种映射,既直接使用物理地址映射和先对基址映射之后,再加上偏移地址,这两种映射的最终地址,都是一样的。
方法一:
#define IOConfig IO_ADDRESS(0x200f0000) //IO复用寄存器
#define HW_REG(reg) *((volatile unsigned int *)(reg))
HW_REG(IOConfig + 0x138) = 0;
上面的代码,是实现 200f_0000 基址地址的映射之后,再加上偏移地址。
方法二:
writel(0, IO_ADDRESS(0x200F0138));
实现是对物理地址做完映射之后,进行写入 0 的操作。
两种方法,经过验证,最终得到的虚拟地址是完全一样的,本平台使用的是 海思的 HI3518平台,所以关于 IO_ADDRESS 的定义是位于
Io.h (arch\arm\mach-hi3518\include\mach) 这里,里面看到 IO_ADDRESS 定义为:
#define __io(a) __typesafe_io(a)
#define __mem_pci(a) (a)#define HI3518_IOCH1_PHYS 0x10000000 /* 0x1000_0000 ~ 0x1020_0000 */
#define HI3518_IOCH2_PHYS 0x20000000 /* 0x2000_0000 ~ 0x2020_0000 */
#define HI3518_IOCH1_SIZE 0x200000
#define HI3518_IOCH2_SIZE 0x700000#define HI3518_IOCH1_VIRT 0xFE000000
#define HI3518_IOCH2_VIRT (HI3518_IOCH1_VIRT + HI3518_IOCH1_SIZE)/*
* physical addr <---> virtual addr
* [0x1000_0000 ~ 0x1020_0000) <---> [0xFE00_0000 ~ 0xFE20_0000)
* [0x2000_0000 ~ 0x2070_0000) <---> [0xFE20_0000 ~ 0xFE90_0000)
*/
#define IO_IOCH1_OFFSET (HI3518_IOCH1_VIRT - HI3518_IOCH1_PHYS)
#define IO_IOCH2_OFFSET (HI3518_IOCH2_VIRT - HI3518_IOCH2_PHYS)#define IO_ADDRESS(x) ((x) >= HI3518_IOCH2_PHYS ? (x) + IO_IOCH2_OFFSET\
: (x) + IO_IOCH1_OFFSET)
1.3、计算虚拟地址
当IO_ADDRESS(0X200F0000) = (0X200F0000) + IO_IOCH2_OFFSET
= (0X200F0000) + (HI3518_IOCH2_VIRT - HI3518_IOCH2_PHYS)
= (0X200F0000) + (HI3518_IOCH2_VIRT - 0x20000000)
= (0X200F0000) + (0xFE000000 + 0x200000) - 0x20000000
= FE2F0000
所以,IO_ADDRESS(0X200F0000) + 0x138 = FE2F0138当当IO_ADDRESS(200F0138) = (200F0138) + IO_IOCH2_OFFSET
= 200F0138 + (HI3518_IOCH2_VIRT - HI3518_IOCH2_PHYS)
= 200F0138 + (HI3518_IOCH1_VIRT + HI3518_IOCH1_SIZE) - 0x20000000
= 200F0138 + 0xFE000000 + 0x200000 - 0x20000000
= FE2F0138可见最终的虚拟地址,都一样的。
1.4、虚拟地址的操作
对已经完成虚拟地址操作之后,需要对这个地址操作的时候,有以下方法:
void writeb(unsigned value, address); // 8位
void writew(unsigned value, address); // 16 位
void writel(unsigned value, address); // 32 位
这些写入函数,完成写 value 到 address 的地址,address 地址都是已经完成映射的虚拟地址
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
读出函数,是将虚拟地址 address 处,读出寄存器的值。
#define HW_REG(reg) *((volatile unsigned int *)(reg))
HW_REG(IOConfig + 0x138) = 0;
通过,宏代码的方法实现寄存器的写入
#define GPIO0_6_DIR IO_ADDRESS(0x20140000+ 0x400)
flag = HW_REG(GPIO0_6_DIR);
完成寄存器数据的读出。
1.5、通过报错的方法寻找头文件
笔者,想寻找关于 IO_ADDRESS 是在那个头文件定义的,经过朋友指点才找到,但是还有通过重复定义的方法,编译器会提供相关的信息,其实就可以找到。
在函数里面,自己定义 IO_ADDRESS ,而头文件包含的地方,也是存在 IO_ADDRESS 的,所以就出现了重复定义的错误,当编译的时候,就会报错:
/home/carlos/3516C/driver/gpio_newBlue/gpio.c:168: warning: "IO_ADDRESS" redefined
arch/arm/mach-hi3518/include/mach/io.h:25: note: this is the location of the previous definition可见,与真正的头文件是在,arch/arm/mach-hi3518/include/mach/io.h,达到我们的目的