S3C2440 VXWORKS 移植笔记
2008-06-21 22:51:48| 分类: 工作技术-嵌入式|举报|字号 订阅
2006 年下半年,我们在自己设计的 BCNG2440 开发板上移植了 VXWORKS。
移植的过程参考了网络上一些 BSP 代码,与现有的代码不同的是,我们的 BSP
实现了内存的重映射,中断向量的重新分配;以及 cache 和 MMU 的开启。移植
的目的是用于一个数据采集系统,ARM 从 FPGA 读取解调数据,通过 100M 网
络发送到服务器。经过测试,使用 UDP 发送速率达到了 43Mbps,使用 TCP 发
送速率为 20Mbps。之前,我们在类似的项目中使用了 MPC8260 为 CPU,当
时 TCP 最高速率也只达到了 20 多 Mbps,因此,我们认为在某些项目中使用
ARM 替代 MPC8260 是可行的。
移植的过程分为一下几个部分:
1)异常处理
2)中断
3)MMU
4)DM9000 网卡驱动
5)启动参数保存问题
6)CPU 设置
7)其他问题
异常处理问题
通常来讲,在嵌入式系统启动之初,CPU 将从 0 地址处(或 ffff0000 处)开
始执行代码,因此初始时将 ROM 映射在 0(或 ffff0000)地址处,当进行完必要
的初始化并启动异常处理机制后,会将 RAM 映射到 0(或 ffff0000)地址处,而
将 ROM 映射到其它地址。
这是因为发生异常时,CPU 会跳转到 0 地址开始处执行异常向量表。由于
RAM 的访问速度远远高于 ROM,因此将 RAM 映射到 0 地址后,可以减少异常
处理的延迟时间还有其他好处,动态和灵活等。
地址重映射可通过不同的方式实现,例如 MPC8260 可通过重新为每个
memory bank 分配地址空间来实现,对于 AT91RM9200,可通过设置其独有的
“REMAP”控制位来实现。
ARM 的体系结构规定在异常发生时,要从 0 地址开始处读取相应的处理指
令,然而 S3C2440A 的固定地址空间管理方法在 VxWorks 里会遇到问题。因为
从硬件上讲,S3C2440A 的地址空间是不能重分配的,它也不支持所谓的 REMAP
功能,一旦硬件连线决定了其 RAM 基地址为 0x30000000(nGCS6),0 地址上为
ROM(nGCS0),就无法再更改。因此必须采用其他办法来解决异常向量表的访问
问题。VxWorks 管理的 RAM 中异常向量表结构如下图:
对于 BOOTROM 来说,不会使用到 MMU,访问地址 0 就是访问 ROM,因
此需要将异常向量表建立在启动 ROM 的开始处。
基本思想是在 Flash 存储器的起始地址硬编码异常入口,仿 vxWorks 建立异
常向量表。异常发生时,经 Flash 存储器入口,跳转到自定义函数,再跳转到 RAM
中异常入口,再调用 vxWorks 提供的异常处理函数。中断处理流程和中断向量
表如下图示意。新异常向量表和原 VxWorks 设计完全一样。
romInit.c(下面是处理 IRQ 异常的例子,其它见源代码):
_romInit:
B
B
B
B
B
B
B
B
cold
_romUndef
_romSwi
_romPrefetch
_romDataAbort
_romReserved
_romIRQ
_romFIQ
_ARM_FUNCTION(romIRQ)
_romIRQ:
sub sp, sp, #4
stmfd sp!, {r0}
ldr r0, L$_promIRQ
ldr r0, [r0]
str r0, [sp, #4]
ldmfd sp!, {r0, pc}
L$_promIRQ:
.long
S3C_EXC_BASE + 20
/* exception base */
#define S3C_EXC_BASE
0x30000100
config.c 中定义:
#define S3C_EXC_BASE
0x30000100
sysLib.c:
添加以下函数声明
void s3cExcVecSet(void);
IMPORT VOIDFUNCPTR excEnterUndef;
IMPORT VOIDFUNCPTR excEnterPrefetchAbort;
IMPORT VOIDFUNCPTR excEnterDataAbort;
IMPORT VOIDFUNCPTR excEnterSwi;
IMPORT VOIDFUNCPTR intEnt;
添加函数 s3cExcVecSet()
void s3cExcVecSet(void)
{
int i;
i = (int)&excEnterUndef;
*((volatile int*)(S3C_EXC_BASE + 0x0)) = i;
i = (int)&excEnterSwi;
*((volatile int*)(S3C_EXC_BASE + 0x4)) = i;
i = (int)&excEnterPrefetchAbort;
*((volatile int*)(S3C_EXC_BASE + 0x8)) = i;
i = (int)&excEnterDataAbort;
*((volatile int*)(S3C_EXC_BASE + 0xc)) = i;
i = (int)&intEnt;
*((volatile int*)(S3C_EXC_BASE + 0x14)) = i;
return;
}
在 sysHwInit()中调用 s3cExcVecSet()
void sysHwInit(void)
{
/* install the IRQ/SVC interrupt stack splitting routine */
_func_armIntStackSplit = sysIntStackSplit;
#ifdef INCLUDE_SERIAL
/* initialise the serial devices */
sysSerialHwInit ();
/* initialise serial data structure */
#endif
/* INCLUDE_SERIAL */
s3cExcVecTblInstall();
s3cExcVecSet();
}
简单的描述上述代码的过程:当异常发生时,以 IRQ 异常为例,PC 指针首
_romIRQ”,然后进入到_romIRQ 函数,此
先跳转到 0x14 处,读取指令“B
函数作用是将 S3C_EXC_BASE + 20 地址里面的值赋给 PC 寄存器
(S3C_EXC_BASE + 20 地址里面存放 vxWorks 的 IRQ 处理函数 intEnt 的地址,
这由 s3cExcVecSet 建立),从而跳转到 inrEnt 函数处理 IRQ 异常。
_romIRQ 函数比较复杂,其作用是将 S3C_EXC_BASE + 20 地址里面的值赋
给 PC 寄存器,此段处理有一定的技巧,先将当前寄存器的内容及 RAM 中的中
断向量的地址入栈,再从堆栈中将中断向量地址装载到 PC,寄存器内容也从堆
栈中恢复过来。
其实不经过堆栈直接将 RAM 中中断向量的地址装载到 PC 应该也可以,但
是有人在论坛中发贴说这样做存在问题,我没有验证过,但是北理他们就是这样
做的(但也通过 r0 转了一次),除此之外我所见到的所有 s3c44b0 和 s3c2410
的 BSP 都是采用前面描述的做法。
对于 vxWorks 映象,如果不采用 MMU 或者使用 MMU 但是进行平坦地址映
射,ROM 仍处于地址 0,因此使用和 BOOTROM 同样的方式处理异常向量即可,
无需对代码做其他修改。但是如果通过 MMU 进行非平坦地址映射(即 SDRAM
映射到地址 0,以加快异常处理速度,ROM 映射到某一空闲地址处,如
0xf0000000),则需要在 SDRAM 起始地址处建立异常向量跳转表。
已经知道,在 vxWorks 内存的 0x0100 偏移处存在异常函数处理指针(由
s3cExcVecSet()建立的),因此需要在 SDRAM 起始地址处填写如下代码(反汇
编后形式):
ldr
ldr
ldr
ldr
pc,=0xf00000fc
/* 复位,此时无用,don't care */
pc,=0x00000100
pc,=0x00000104
pc,=0x00000108
ldr
ldr
ldr
ldr
pc,=0x0000010c
pc,=0x00000110
/* 保留,don't care */
pc,=0x00000114
pc,=0x00000118
/* vxWorks 不使用 FIQ,don't care */
获取上面汇编代码对应的机器码
0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe
59ff0f4(至于为什么全是一样的数据,我也没搞很清除,估计应该是 ldr 的实现
机理决定的)。
将这 8 个 DWORD 写入到 SDRAM 起始地址处即完成异常向量跳转表的创
建;
在 sysLib.c 中添加函数
void s3cExcVecTblInstall()
{
int i;
long excVecTbl[] =
{0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4};
for(i=0;i<8;i++)
*((volatile unsigned long*)(LOCAL_MEM_LOCAL_ADRS + i*4)) = excVecTbl[i];
{
}
return;
}
并在 sysHwInit()中调用 s3cExcVecSet()
void sysHwInit(void)
{
/* install the IRQ/SVC interrupt stack splitting routine */
_func_armIntStackSplit = sysIntStackSplit;
#ifdef INCLUDE_SERIAL
/* initialise the serial devices */
sysSerialHwInit ();
/* initialise serial data structure */
#endif
/* INCLUDE_SERIAL */
s3cExcVecTblInstall();
s3cExcVecSet();
}
其他一些说明:
对于异常向量表的建立和处理,本应该由 vxWorks 来完成,事实上,在
bootConfig.c 中的 usrIni ()函数中,有 intVecBaseSet()和 excVecInit()函数调用,不
过对于 ARM 体系的 CPU 来说,由于不存在 vector base register,因此
intVecBaseSet 实际上是一个空操作(分析汇编可看出就是一个 return);而由于
调用 excVecInit()函数时,处于 0x100(异常函数处理指针)地址处的是 ROM(此
函数调用在 MMU 启用之前,因此即使在 vxWorks 映象中启用 MMU 并进行非
平坦地址映射,0x100 还是对应 ROM),因此尽管 excVecInit 运行了,但是没
起到实际作用,也就是没能在 SDRAM 偏移 0x100 处建立异常函数处理指针。
s3cExcVecSet()和 rom 中写入的异常向量编码(对于 BOOTROM 和 vxworks
平坦地址映射)以及 s3cExcVecTblInstall(对于 vxworks 非平坦地址映射)实际上
起到了取代 excVecInit()的作用。
从 bootConfig.c 代码中可以看出,intVecBaseSet()和 excVecInit ()之后紧接着
调用了 sysHwInit (),因此在 sysHwInit 调用 s3cExcVecSet 和 s3cExcVecTblInstall。
(更多关于异常向量的内容可参见“44B0x 的 BSP 是如何调成的”以及 Amine 的“S3C44B0X
VxWorks BSP 移植笔记”)。
中断
ARM 没有专门的中断控制器,对于中断的操作只能通过操作有限的几个寄存器实现。并
且由于不同厂家生产的不同型号的 ARM 处理器提供的中断寄存器是不一样的,因此 vxWorks
没有为 ARM 提供中断控制的库函数,这样一来,就需要在 BSP 里面自己编写中断控制程序。
在我们的 BSP 里面,中断控制程序位于 s3c2440aIntrCtl.c,程序比较简单,主要是进
行中断寄存器的初始化以及实现并注册三个回调函数。但是由于中断控制实现的特殊性,有
必要说明编写 ISR 时要注意的问题,建议编写 ISR 时此节必看。
1.关于中断 enable/disable
对于 s3c2440a,enable/disable 中断本质上就是对相应中断的掩码位进行操作(INTMSK
寄存器),但是在应用程序中应该调用 intEnable()和 intDisable()函数来实现,而不是直
接操作 INTMSK 寄存器。
这是因为调用 intEnable()和 intDisable()时会通过回调函数最终调用到
s3c2440aIntLvlEnable()和 s3c2440aIntLvlDisable(),从源代码中可以看见,存在一个全
局变量 s3c2440aIntLvlEnabled,它记录了当前打开和关闭的中断的情况,enable 和 disable
操作都是现对这个变量相应位赋值,然后将其赋给 INTMSK 寄存器。如果用写地址方式直接
修改 INTMSK 寄存器,那么下一次调用 intEnable()或 intDisable()时会使直接修改的控制
位会到以前的情况,从而使得中断控制操作无效。
这个问题在我调试中断时遇到过,当时使用直接操作 INTMSK 寄存器的方式,发现外部
中断 INT4-7 对应的比特位在使能后不久就被 disable 了,后来使用 ADS 跟踪执行汇编代码,
发现当执行 printf 语句时,由于 printf 向控制台打印信息会产生串口中断,在串口中断执
行过程中使得 INT4-7 对应的比特位被 disable 掉,这才发现时直接操作 INTMSK 寄存器惹得
祸。
2.带有子中断的中断操作
如串口中断等,存在发送中中断、接受中断和错误中断 3 个子中断。以串口中断为例,
当中断发生时,会先执行 s3c2440aIntLvlVecChk()函数(不是很清楚这个过程,但是跟踪
汇编代码可以肯定这点),在 s3c2440aIntLvlVecChk 里面首先将 UART 中断的 3 个子中断全
部禁止掉(通过设置 INTSUBMSK 对应控制位),然后清除 SRCPND 和 INTPND 寄存器中相应的
比特位。接下来就进入到 ISR,在 ISR 里面,通过读取 SUBSRCPND 寄存器,判断当前发生的
是哪一个子中断,然后进行相应的处理并清除 SUBSRCPND 寄存器中相应的比特位,最后设置
INTSUBMSK 寄存器打开需要的子中断。
从上面的流程可以看出,中断的禁止与重新打开实际上是对子中断掩码进行操作来完
成的,并没有涉及到 INTMSK 的操作(intEnable/intDisable)。