嵌入式 vxworks

来源:互联网 发布:软件套什么定额 编辑:程序博客网 时间:2024/05/21 07:16
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可通过沉新为每个memorybank分配地址空间来实现,洞口AT91RM9200,可通过设置其独有的“REMAP”控制位来实现。 

  ARM的体系结构规定在异常发生时,要从0地址开始处读取相应的处理指令,然而S3C2440A的固定地址空间办理方法在VxWorks里会遇到问题。因为从硬件上讲,S3C2440A的地址空间是不能沉分配的,它也不支持所谓的REMAP功能,一旦硬件连线决定了其RAM基地址为0x(nGCS6),0地址上为ROM(nGCS0),就无法再更改。因此必需采用其他办法来解决异常向量表的访问问题。VxWorks办理的RAM中异常向量表结构如下图: 

  洞口BOOTROM来说,不会使用到MMU,访问地址0就是访问ROM,因此需要将异常向量表建立在启动ROM的开始处。 

  基本念想是在Flash存储器的起始地址硬编码异常入口,仿vxWorks建立异常向量表。异常发生时,经Flash存储器入口,跳转到自定义函数,再跳转到RAM中异常入口,再调用vxWorks提供的异常处理函数。中断处理流程和中断向量表如下图示意。新异常向量表和原VxWorks设计完全一样。 

  romInit.c(下面是处理IRQ异常的例子,其它见流代码): 

  _romInit: 

  B cold 

  B _romUndef 

  B _romSwi 

  B _romPrefetch 

  B _romDataAbort 

  B _romReserved 

  B _romIRQ 

  B _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 

  #define S3C_EXC_BASE 0x 

  config.c中定义: 

  #define S3C_EXC_BASE 0x 

  sysLib.c: 

  添加以下函数声明 

  void s3cExcVecSet(void); 

  IMPORT VOIDFUNCPTRexcEnterUndef; 

  IMPORT VOIDFUNCPTRexcEnterPrefetchAbort; 

  IMPORT VOIDFUNCPTRexcEnterDataAbort; 

  IMPORT VOIDFUNCPTRexcEnterSwi; 

  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) 

  { 

  _func_armIntStackSplit =sysIntStackSplit; 

  #ifdef INCLUDE_SERIAL 

  sysSerialHwInit (); 

  #endif 

  s3cExcVecTblInstall(); 

  s3cExcVecSet(); 

  } 

  简单的描述上述代码的过程:当异常发生时,以IRQ异常为例,PC指针首先跳转到0x14处,读取指令“B_romIRQ”,然后进入到_romIRQ函数,此函数作用是将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 pc,=0xf00000fc 

  ldr pc,=0x00000100 

  ldr pc,=0x00000104 

  ldr pc,=0x00000108 

  ldr pc,=0x0000010c 

  ldr pc,=0x00000110 

  ldr pc,=0x00000114 

  ldr pc,=0x00000118 

  获取下面汇编代码对应的机器码0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4(至于为什么全是一样的数据,我也没搞很清除,估计应该是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) 

  { 

  _func_armIntStackSplit =sysIntStackSplit; 

  #ifdef INCLUDE_SERIAL 

  sysSerialHwInit (); 

  #endif 

  s3cExcVecTblInstall(); 

  s3cExcVecSet(); 

  } 

  其他一些说明: 

  对于异常向量表的建立和处理,本应该由vxWorks来完成,事实上,在bootConfig.c中的usrIni()函数中,有intVecBaseSet()和excVecInit()函数调用,步椠对于ARM体系的CPU来说,由于不存在vectorbaseregister,因此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)。 

  对于其它带有子中断的中断类型,建议也按照这个方式进行处理,具体可以参考串口驱动的相关代码。另外,由于目前不清楚其他带有子中断的中断类型的具体情况,因此s3c2440aIntLvlVecChk()函数里只有串口中断处理的相关代码,其它的情况暂时留空,如果需要编写相应的ISR,切记完善s3c2440aIntLvlVecChk()函数。 

  3.外部中断EINT4_7和EINT8_23 

  在INTMSK、INTPND和SRCPND寄存器里面,外部中断EINT4_7和EINT8_23分别只对应一个控制位,然后在EINTMASK、EINTPND再对具体是那一个外部中断进行处理。 

  与带有子中断的中断操作的中断操作类似,当某一外部中断产生时(以EINT6为例),先执行s3c2440aIntLvlVecChk()函数,在s3c2440aIntLvlVecChk里面首先将EINTMASK中EINT4~EINT7对应的控制位局部禁止掉,然后清除SRCPND和INTPND寄存器中相应的比特位。接下来在ISR里面进行了需要的操作之后,清除EINTPEND寄存器相应比特位,然后设置EINTMASK以打开EINT6的中断。 

  目前的操作是针对EINT4_7或EINT8_23中仅有一个外部中断发生的情况。以EINT4_7为例,如果存在多个外部中断源可能产生中断,则情况比较复杂,对这种情况没有做过测试,因此也无法得到切实可用的代码,但是可能的操作描述如下,仅供将来参考:在s3c2440aIntLvlVecChk()中禁止EINT4~EINT7对应的控制位(EINTMASK中)之前,先使用一个全局变量(例如eintMaskStatus)保存EINTMASK中EINT4~EINT7的当前值。对于ISR来说,需要使用一个总的ISR处理EINT4~EINT7所有的中断,在进入该ISR后,先通过读取EINTPND判定当前发生的是哪一个外部中断,接下来在完成相应操作之后清除EINTPND相应比特位,最后根据eintMaskStatus的值恢复EINTMASK中EINT4~EINT7的值(即打开相应的掩码位)。 

  这样做是因为EINT4~EINT7显然处于同一优先级,当EINT4(例如)来中断时,必需禁止EINT5~EINT7。采用全局变量eintMaskStatus(在ISR所在源文件中要用extern声明)是为了确保不会误disable其它需要的外部中断。另外,在EINT4_7的ISR结束处恢复各个相应外部中断掩码时要注意不能干扰EINT8_23对应的外部中断掩码,反之亦然,以免产生误操作。 

  4.其它的中断(包括外部中断0、1、2、3) 

  其它的中断(包括外部中断0、1、2、3)在INTMSK、INTPND和SRCPND寄存器里面都有自己的控制位,因此操作相对简单。此时,若发生中断,由于s3c2440aIntLvlVecChk()中清除了SRCPND和INTPND寄存器中相应的比特位,因此在ISR中无需再做其它操作。步椠在进入和退出ISR时加上intEnable和intDisable似乎也没什么问题。 

  5.例子 

  对于带有子中断的中断操作可以参照串口中断。 

  对于外部中断EINT4_7和EINT8_23,可以参照网络驱动(eint7)和intTest.c(eint6)。在intTest.c中,eintTestInit()、eTestInt()、eintTest()是外部中断6的测试代码。若在BOOTROM中进行测试,在syslib.c中合适位置处添加#include"intTest.c",在bootConfig.c的合适位置处添加eintTest()的声明,并在usrRoot()函数的最后一行代码(taskSpawn……)之前添加eintTest()的调用。另外必需在CPLD中将BOTTON1指定到EINT6 (EINT6<= notBOTTON1),并焊接RP552、D551和S551。当BOOTROM进行倒数计数时按任意键进入命令行模式,此时按botton1可以在超级中断看见打印信息“interrupt:eTestInt6,entered”。对于其它的中断,以外部中断2为例,可以参考intTest.c(eint2),其中eintTestInit2()、eTestInt2()、eintTest2()是外部中断2的测试代码。若在BOOTROM中进行测试,在syslib.c中合适位置处添加#include"intTest.c",在bootConfig.c的合适位置处添加eintTest2()的声明,并在usrRoot()函数的最后一行代码(taskSpawn……)之前添加eintTest2()的调用。另外必需在CPLD中将BOTTON2指定到EINT2 (EINT2<= notBOTTON2),并焊接RP552、D552和S552。当BOOTROM进行倒数计数时按任意键进入命令行模式,此时按botton2可以在超级中断看见打印信息“interrupt: eTestInt2,entered”。MMU只在vxWorks中才使用,BOOTROM不会用到。在S3C2440A中使用MMU,需要异常向量处理的配合,详见“异常向量”相关部分的说明。 

  启用MMU时,要在TORNADO里面vxWorks组建配置串口包含hardwareàmemoryàMMUàMMUModeàbasic MMU support或full MMUsupport。 

  sysLib.c中的sysPhysMemDesc可配置是使用平坦地址映射还是非平坦地址映射,使用非平坦地址映射时,由于异常向量跳转表存在SDRAM中,访问速度快于ROM,因此有利于提高系统性能。 

  若使用MMU时,需要反确配置sysPhysMemDesc数组。对任意内存空间的访问,必需在sysPhysMemDesc配置相应的项。若添加新的外设,该外设对应的内存空间必须在sysPhysMemDesc中配置。网络芯片采用DAVICOM的DM9000E。驱动程序采用DAVICOM提供的v1.11版本,但是他们给的驱动是DM9000A并且基于x86的,移植自linux,并且其版本最后修反时间06年4月,存在不少问题,因此这方面的工作次要是对他们的驱动进行修改个完善,以适应我们的系统。 

  1.由于原来的驱动是基于x86的,因此对于DM9000_IN_ADDR、DM9000_IN_ADDR、DM9000_OUT_ADDR、DM9000_IN_BYTE、DM9000_OUT_BYTE、DM9000_IN_WORD、DM9000_OUT_WORD、DM9000_OUT_CHAR、DM9000_IN_CHAR这些宏需要重泻义。 

  2.关于CPU中相关寄存器的初始化。DM9000E所占用的memorybank的访问属性在BSP中的romInit中已经设置,在其驱动里面的不再做相关工作。为DM9000分配外部中断号、设置外部中断产生方式等操作在cpuForDM9000Init()函数里定义,该函数在dm9000Start()中调用。另外在dm9000Stop()中调用cpuForDm9000disable(),作用是关闭eint7对应的外部中断掩码。 

  3.在configNet.h中将DM9K_LOAD_STRING改为"0x4:0x4",这是CPU中EINT4-7的中断级别,另外删除dm9000end.c中dm9000Parse()函数里面关于IOBase的部分。因为arm不存在IO访问的说法。 

  4.原来的驱动是从SROM中获取MAC地址,我们不使用MAC地址,因此在程序里面定义全局变量DM9KdefaultMacAddr,用来记录MAC地址,并在dmfe_reset_dm9000()里面将其赋给dev->enetAddr。需要注意的是,当在以太网上使用多块板子时,要注意为每块板子修改MAC地址(重烧BOOTROM和重编译vxWorks),否则多块板子会使用同一个MAC地址,从而出现网络不同的情况。 

  5.32bit对齐问题。原来的驱动是基于x86的,x86不存在对齐问题,因为x86硬件可以自动将程序中没有对齐的中央对齐。但是移植到ARM上就有问题。主要是typedefstructend_device{…}END_DEVICE这个结构。它的最后一个成员txBuf没有处于32bit对齐的位置,这样在将其强制类型转化成word时(dmfe_start_xmit()函数发送数据时)会访问到非法数据。解决办法是在END_DEVICE结构体的txBuf成员以前添加 

  UCHAR dummyBytes[3]; 

  从而使得txBuf处于32bit对齐的位置。DummyBytes不做其它用途。 

  typedef struct end_device 

  { 

  END_OBJ end; 

  END_ERR err; 

  UINT IOBase; 

  int unit; 

  int ivec; 

  int ilevel; 

  long flags; 

  UCHAR enetAddr[6]; 

  CACHE_FUNCS cacheFuncs; 

  CL_POOL_ID pClPoolId; 

  BOOL rxHandling; 

  UCHAR io_mode; 

  char tx_pkt_cnt; 

  char device_wait_reset; 

  UWORD queue_pkt_len; 

  UCHAR reg0; 

  UCHAR nic_type; 

  UCHAR op_mode; 

  UCHAR mcastFilter[8]; 

  UCHAR dummyBytes[3]; 

  UCHAR txBuf[ETHERMTU + EH_SIZE + 6 + 64]; 

  } END_DEVICE; 

  6.中断处理。在ISR里面要将x86中断操作的内容删掉,然后加入ARM下处理外部中断的操作。原理可过程可参考“中断”一节的内容,另外,目前的操作是当ISR退出时先开CPU中断,再开DM9000E的片上中断,不知道反过来行不行,会不会有什么影响。 

  7.发送处理。通过对比发现,原来的驱动移植自linux,但是移植的却不完全,一个很大的问题体现在发送处理下面。 

  DM9000E有两个发送缓存,当发送一个缓存中的数据时,可以向另一个缓存写数据,以加快传送速度。而DAVICOM提供的驱动里面,只用到了一个缓存: 

  static int dmfe_start_xmit( PKT skb, END_DEVICEdev) 

  { 

  …… 

  …… 

  …… 

  dev->queue_pkt_len =skb->len; 

  dev->tx_pkt_cnt++; 

  DM9000_OUT_CHAR( 0xfc, skb->len &0xff ); 

  DM9000_OUT_CHAR( 0xfd, (skb->len>> 8) & 0xff); 

  DM9000_OUT_CHAR( 0x2, 0x01 ); 

  return 0; 

  } 

  这样以来,要是CPU向DM9000E发送数据的速度较快时,一个缓冲区会忙不过来,就会出现“txfull”,然后网卡工作不反常,无法继续进行通讯。为了实现稳定告诉的通讯,需要修改发送过程,参考linux下DM9000E的驱动程序,对vxWorks驱动修改如下: 

  static int dmfe_start_xmit( PKT *skb, END_DEVICE*dev) 

  { 

  …… 

  …… …… if (dev->tx_pkt_cnt == 0) {dev->tx_pkt_cnt ++; DM9000_OUT_CHAR( 0xfc,skb->len & 0xff ); DM9000_OUT_CHAR(0xfd, (skb->len >> 8)& 0xff ); DM9000_OUT_CHAR( 0x2, 0x01 ); } else {dev->queue_pkt_len = skb->len;dev->tx_pkt_cnt ++; } return 0; }还要修改dm9000Int() 

  static void dm9000Int( END_DEVICE *pDrvCtrl) 

  { 

  …… 

  …… 

  …… 

  if( isr_status & 2) 

  { 

  DRV_LOG (DRV_DEBUG_INT, "Got an TX interrupt!",pDrvCtrl->tx_pkt_cnt, 2, 3, 4, 5,6); 

  DRV_SP( DRV_PK,"x" ); 

  DM9000_IN_CHAR(0x1,TX_comple_status); 

  if (TX_comple_status &0xc) 

  { 

  pDrvCtrl->tx_pkt_cnt--; 

  if (pDrvCtrl->tx_pkt_cnt >0) 

  { 

  DM9000_OUT_CHAR( 0xfc, pDrvCtrl->queue_pkt_len& 0xff ); 

  DM9000_OUT_CHAR( 0xfd, (pDrvCtrl->queue_pkt_len>> 8) & 0xff); 

  DM9000_OUT_CHAR( 0x2, 0x01 ); 

  } 

  } 

  } 

  …… 

  …… 

  …… 

  } 

  修改后,不会再出现网络不稳定的情况(并且可以ping大包了)。使用赵海涛的程序测试网络速度(TCP),发现使用MMU时(非平坦地址映射),可达22Mbps左右;关闭MMU但是使能cache时,略大于10Mbps;同时关闭MMU和cache,只有略大于6Mbps的速度。使用平坦地址映射MMU模式下的网络速度没有测试。 

  8.延时问题。linux的驱动采用uDelay进行延时,但是DAVICON提供的vxWorks驱动对这个问题处理不好,从而使得延时过长,影响运行速度。考虑到默认情况下taskDelay(1)延时0.01s,已远大于DM9000任何操作请求的最小延时,因此在需要延时的中央改为taskDelay(1)。
0 0
原创粉丝点击