曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 7985|回复: 0
打印 上一主题 下一主题

[2410] __REG2, __REG(), __REGP() 宏的解析

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-11-2 15:52:28 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在前面看到的几个宏,如 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 <stdio.h>

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
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-3 13:37 , Processed in 0.065015 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表