__REG2,__REG(),__REGP() 宏的解析|SAMSUNG-曲径通幽 - P...

来源:百度文库 编辑:神马文学网 时间:2024/04/29 07:29:15
__REG2, __REG(), __REGP() 宏的解析
在前面看到的几个宏,如 GPCON(x), GPDAT(x)等,最终要涉及到 __REG(), REGP()更底层的宏。这里对这些宏进行解析(属于个人理解,不一定正确,如果有误,高手看到请斧正,不胜感激)。像 GPCON(x) 宏的作用就是从物理地址到虚拟地址的一个转换过程。
以 GPCON(x) 宏的展开,顺藤摸瓜。GPCON(x) 的定义为:
引用
#define GPCON(x) __REG2(0x56000000, (x) * 0x10)
上面,0x56000000 是寄存器 GPACON 的物理地址,在 8 组 GPIO 寄存器中,相对而言,它就是一个基地址。x 表示是寄存器组的偏移,比如 x 无偏移时(x=0),对应的地址就是 0x56000000, x 偏移 1 时 (x=1),对应的地址就是 0x56000010 ,这个地址也就是 GPBCON 寄存器的地址。
__REG2() 的定义是:
引用
# define __REG2(x,y)   ( __builtin_constant_p(y) ? (__REG((x) + (y))) : (*(volatile u32 *)((u32)&__REG(x) + (y))) )
上面,__builtin_constant_p() 是 GCC 编译器的一个内建函数(详见:http://www.groad.net/bbs/read.php?tid-1304.html)
__REG2(x,y) 有两个参数,第一个参数 x 是一个“相对基地址”,之所以起这个名称,是因为它是在一定范围内(比如GPIO控制寄存器组里)作为一个基地址; y 参数是一个偏移量。用 GPCON(2) 来举例,就是__REG(0x56000000,2 * 0x10) 。
那么 __builtin_constant_p(0x20) ,这个 0x20 并不是一个事先定义好的宏,所以在整个 __REG2(x,y) 展开的表达式中,取后者即:
引用
(*(volatile u32 *)((u32)&__REG(x) + (y)))
从上面可见,__REG2() 宏,已经演变成了对 __REG(x) 的应用。从这个宏的形式可以知道,它实际上就是要取得 0x56000020 这个寄存器里的值罢了,只是这个定义形式上看有点让人眼花。只不过这里,先是将物理地址转化为虚拟地址后,再取值,但本质却都是拿同样的东西。
__REG(x) 的定义为:
引用
#define __REG(x)        __REGP(io_p2v(x))
io_p2v(x) 的定义为:
引用
#define io_p2v(x) ((x) | 0xa0000000)
从 io_p2v(x) 的名字也可以望文生义,p2v 即 physics to virtual 。这里,物理地址到虚拟地址的转换只是将物理地址加上一个 0xa0000000 的偏移量,从hardware.h 文件的一个注释中可看到这一点:
引用
*
* S3C2410 internal I/O mappings
*
* We have the following mapping:
*              phys            virt
*              48000000        e8000000
*/
#define VIO_BASE                0xe8000000      /* virtual start of IO space */
#define PIO_START               0x48000000      /* physical start of IO space */
上面,0xe8000000 - 0x48000000 = 0xa0000000 .
在 io_p2v() 里或上一个 0xa0000000 也就相当于加上一个 0xa0000000 。
为了继续展开 _REGP() 宏,使用一个更普通点的地址,如 0x56000004 (这是GPADAT 寄存器的地址),REGP() 的定义为:
引用
#define __REGP(x)       ((__regbase *)((x)&~4095))->offset[((x)&4095)>>2]
__regbase 是一个有着 4096 个整型值的数组。假设这里的 x 是 io_p2v(0x56000004)=0xF6000004 。这样,((x)&~4095) 就为 0xF6000000 ,然后 (__regbase *)((x)&~4095) ,那么这里的意思是,令 0xF6000000 成为一个指向 __regbase 结构体的指针(地址),也就是 offset[4096] 数组的首地址,同时也是强制 4K 空间边界对齐。
在 offset[] 数组中, ((x)&4095)>>2 代入 x=0xF6000004 ,就为 (0xF6000004&4095)>>2 ,结果为 1 ,也就是说取元素 offset[1] 。
综合上面,完整的 __REGP() 宏的最后结果就是: (__regbase *)0xF6000000->offset[1]
再回头看 __REG2(x,y) 宏中的 (u32)&__REG(x) 部分,从中得知,得到的是一个首地址为 0xF6000000 的数组的第 2 个元素 offset[1] 的地址,这个地址的值就是 0xF6000004 .
呵呵,貌似是兜了个小圈子,那为什么这么做呢,根据 hardware.h 里的注释是这样的:
引用
/*
* This __REG() version gives the same results as the one above, except
* that we are fooling gcc some how so it generates far better and smaller
* assembly code for access to contigous registers. It's a shame that gcc
* doesn't guess this by itself
*/
__REG() 宏给 gcc 耍了个小把戏,让 gcc 在访问连续的寄存器时,能够产生更加紧致的代码。至于代码如何紧凑,可以看下反汇编。
上面的宏可以用搬移到一个应用程序里运行看看:
引用
#include
typedef unsigned int u32;
typedef struct {
volatile u32 offset[4096];
} __regbase;
#define io_p2v(x) ((x) | 0xa0000000)
#define __REGP(x)       ((__regbase *)((x)&~4095))->offset[((x)&4095)>>2]
#define __REG(x)    __REGP(io_p2v(x))
int main()
{
printf ("%x\n", (u32)&__REG(0x56000004));
return (0);
}
运行与输出:
引用
[beyes@localhost s3c2410]$ ./set_gpio.exe
f6000004