中断处理是整个运行系统中优先级最高的代码,可以抢占任何任务级代码运行。中断机制是多任务环境运行的基础,是系统实时性的保证。几乎所有的实时多任务操作系统都需要一个周期性系统时钟中断的支持,用以完成时间片调度和延时处理。VxWorks 提供tickAnnounce(),由系统时钟中断调用,周期性地触发内核。

为了快速响应中断,VxWorks的中断服务程序(ISR)运行在特定的空间。不同于一般的任务,中断服务程序没有任务上下文,不包含任务控制块,所有的中断服务程序使用同一中断堆栈,它在系统启动时就已根据具体的配置参数进行了分配和初始化。在ISR中能使用的函数类型与在一般任务中能使用的有些不同,主要体现在:

(1) ISR中不能调用可能导致blocking的函数,例如:

  1. 不能以semTake获取信号量,因如果该信号量不可利用,内核会试图让调用者切换到blocking态;
  2. malloc和free可能导致blocking,因此也不能使用;
  3. 应避免进行VxWorks I/O系统操作(除管道外);
  4. 应避免在ISR中进行浮点操作;

(2) 在ISR中应以logMsg打印消息,避免使用printf

(3) 理想的ISR仅仅调用semGive等函数,其它的事情交给semTake这个信号量的任务去做。一个ISR通常作为通信或同步的发起者,它采用发送信号量或向消息队列发送一个消息的方式触发相关任务至就绪态。ISR几乎不能作为信息的接收者,它不可以等待接收消息或信号量。

1. 中断服务程序

VxWorks中与中断相关的重要API函数或宏有:

(1)intConnect():中断连接,将中断向量与ISR入口函数绑定

SYNOPSIS STATUS intConnect
(
        VOIDFUNCPTR * vector,/* interrupt vector to attach to  */
        VOIDFUNCPTR  routine, /* routine to be called     */
        int    parameter /* parameter to be passed to routine */
);

intConnect只是调用了下文将要介绍的intHandlerCreate()和intVecSet()函数。

(2)INUM_TO_IVEC(intNum):将中断号转化为中断向量的宏。

与INUM_TO_IVEC对应的还有一个IVEC_TO_INUM(intVec),实现相反的过程。INUM_TO_IVEC和IVEC_TO_INUM的具体定义与特定的BSP有关,例如:

VxWorks实现了与BSD4.4 TCP/IP兼容的网络协议栈,并且其实时性较之有很大提高,这使得基于BSD4.4 UNIX Socket的应用程序可以很方便地移植到VxWorks中去。
1.1 VxWorks网络组件

在最底层,vxworks通常使用以太网作为传输媒介。在传输媒介的上一层,vxworks使用TCP/IP和UDP/IP协议,用于vxworks进程与其他主机环境进程之间的传输数据。

在以太网协议之上,提供几种网络工具:

套接字(Sockets)
允许运行在vxworks或其他主机环境下的任务相互通信。
远程调用
允许一个任务唤醒实际上运行在另一台机器上的过程,调用任务和被调用过程可以是运行在vxworks或其他主机开发系统中。
远程文件访问
允许通过网络文件系统NFS、远程shell(RSH)、文件传输协议(FTP)、TFTP访问远程主机上的文件。
文件输出
允许远程主机通过NFS客户端维护vxworks dos文件系统。
远程命令执行
允许任务通过网络调用在主机开发环境上的命令。

支持以下几种物理连接:

  1. 以太网
  2. 串行线接口协议(SLIP and CSLIP)
  3. 除了使用以太网外,vxworks网络能够使用通过串行线连接的串行线接口协议SLIP或者使用压缩头的SLIP协议(CSLIP)与主机通信。使用SLIP或CSLIP作为网络接口驱动是机器间通过长距离电话线连接或RS232串行线点对点连接使用TCP/IP软件的直接方法
  4. 共享内存网络
  5. vxworks网络可以用于同一个底板的多个处理器间相互通信。在这种方式下,数据的传递是通过共享内存进行的。是通过共享网络驱动程序实现的

TCP/IP是vxworks提供的网络间进程通信的主要机制。

主要包括三个协议:Internet协议、IP协议、传输层协议。

    1. Internet协议:处于TCP和UDP之上的一组协议专门开发的应用程序。包括telenet、文件传输协议(ftp)等。
    2. IP层也称网络层,包括Internet协议(IP)、国际控制报文协议ICMP和地址识别协议ARP。

IP协议是TCP/IP协议族的基础。该协议被设计成互连分组交换通信网,已形成一个网际通信环境。它负责在源主机和目的主机之间传输来自其较高层软件的称为数据报文的数据块,它在源和目的地之间提供非连结型传递服务。

网际控制报文协议(ICMP),实际上并不是IP层部分,但直接同IP层一起工作,报告网络上的某些出错情况。允许网际路由器传输差错信息或测试报文。

地址识别协议(ARP)实际上也不是IP层部分,它处于IP层和数据链路层之间,它是在32位IP地址和48位局域网地址之间执行翻译的协议。操作系统用地址解析协议ARP来允许机器将IP地址转变成真正的硬件地址,如以太网地址。

  1. 传输层协议,包括传输层控制协议。主要有TCP和UDP两种协议。TCP:面向连接的传输控制协议;UDP:无连接的用户数据报协议。

IP协议是一个传输层的协议,其他协议可以用它来传输数据。传输控制协议TCP是一个可靠的端对端的协议,它用IP来传送和接收它自己的包。TCP可靠地传送和接收两应用程序间的数据,并保证数据不会丢失。当用IP来传输TCP包时,IP包的数据段就是TCP包。每一个通信主机的IP层负责传送和接收IP包。用户数据报协议(UDP)也用IP层来传输它的包。

TCP/IP协议,涉及到四层:链路层、网络层、传输层和应用层(从下到上),对应OSI七层模型。

本文讲解了VxWorks操作系统下的PCI总线下的设备相关操作方法以及实现代码!
源程序文件:
vxConfPciDevice.c
函数功能:

已知该类PCI设备的Vendor号,Device号和索引号myIndex,查找出该类PCI设备的MEMORY地址

和IO地址,以及中断级,并添加第一个MEMORY的物理地址到虚拟地址的映射。

调用实例
如果有4块该类PCI设备,则调用四次函数,索引号从0~3,如下:
    for (myIndex=0; myIndex<4; myIndex++)
    {
        myStatus = vxfConfPciDevice(vendorId, devId, myIndex);
    }

 
 
/* 
modification history 
-------------------- 
21aug03,ghb 添加函数功能及调用说明。 
*/ 
 
#include "vxWorks.h" 
#include "stdio.h" 
#include "sysLib.h" 
#include "stdlib.h" 
#include "config.h" 
 
#include  
#include  
 
#ifndef PCI_DEV_MMU_MSK 
#define PCI_DEV_MMU_MSK (~(VM_PAGE_SIZE - 1)) /* Mask MMU page */ 
#endif /* PCI_DEV_MMU_MSK */ 
 
/* 外部函数原型 */ 
extern STATUS sysMmuMapAdd 
    ( 
    void * address, 
    UINT length, 
    UINT initialStateMask, 
    UINT initialState 
    ); 
 
/********************************************************************************** 
* vxConfPciDevice 
* 
* 已知该类PCI设备的Vendor号,Device号和索引号myIndex,查找出该类PCI设备的MEMORY地址 
* 和IO地址,以及中断级,并添加第一个MEMORY的物理地址到虚拟地址的映射。 
* 
* Parameter: 
* vendorId:该类PCI设备的Vendor号 
* devId:该类PCI设备的Device号 
* myIndex:该类PCI设备的索引号 
* 
* Return: 
* OK:查找并映射成功 
* ERROR: 查找或映射不成功 
* 
*/ 
 
STATUS vxConfPciDevice(int vendorId, int devId, int myIndex) 
{ 
 int busno, devno, funcno; 
 int membaseCsr0, membaseCsr1, iobaseCsr; 
 unsigned char irq; 
 STATUS myStatus; 
  
 /* 查找PCI设备 */ 
 myStatus = pciFindDevice(vendorId, devId, myIndex,  
     (int *)&busno, (int *)&devno, (int *)&funcno); 
      
 if (myStatus == OK) /* 找到该类PCI设备 */ 
 { 
    pciConfigInLong (busno, devno, funcno, PCI_CFG_BASE_ADDRESS_0, &membaseCsr0); 
    pciConfigInLong (busno, devno, funcno, PCI_CFG_BASE_ADDRESS_1, &membaseCsr1); 
    pciConfigInLong (busno, devno, funcno, PCI_CFG_BASE_ADDRESS_2, &iobaseCsr); 
/* pciConfigInByte (busno, devno, funcno, PCI_CFG_DEV_INT_LINE, &irq);*/ 
    membaseCsr0 = membaseCsr0 & (~0x0000000f)/*PCI_MEMBASE_MASK*/; 
    membaseCsr1 = membaseCsr1 & (~0x0000000f)/*PCI_MEMBASE_MASK*/; 
/* iobaseCsr = iobaseCsr & PCI_IOBASE_MASK;*/ 
 
    printf("\nbusno=%d, devno=%d, funcno=%d, membaseCsr0=%x, membaseCsr1=%x, iobaseCsr=%d\n",  
    busno, devno, funcno, membaseCsr0, membaseCsr1, iobaseCsr); 
 
#ifdef INCLUDE_MMU_BASIC 
    if (sysMmuMapAdd((void *)(membaseCsr0 & PCI_DEV_MMU_MSK), 0x200000,  
    VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE | 
         VM_STATE_MASK_CACHEABLE, 
         VM_STATE_VALID | VM_STATE_WRITABLE | 
         VM_STATE_CACHEABLE_NOT) == ERROR) 
        { 
         printf("\nGHB_VRAM error\n"); 
         return ERROR; 
        }; 
#endif /* INCLUDE_MMU_BASIC */ 
 
    /* 最后一个参数' | PCI_CMD_MASTER_ENABLE'好像可有可无,没有影响 */ 
    pciConfigOutWord (busno, devno, funcno, 
                    PCI_CFG_COMMAND, PCI_CMD_IO_ENABLE | 
                    PCI_CMD_MEM_ENABLE | PCI_CMD_MASTER_ENABLE); 
    return OK; 
 }  
 else 
 { 
    printf("\nFind VRAM device error\n"); 
    return ERROR; 
 } 
} 
 

 

 

PCI总线规定访问配置空间的总线事务,称为配置读写事务。不同于存储访问事务使用存储地址访问,而是使用ID号来寻址访问PCI配置空间。

PCI设备的ID号由总线号(BUS NUMBER)、设备号(DEVICE NUMBER)和功能号(FUNCTION NUMBER)组成。

对于PowerPC的嵌入式应用,PCI设计往往很简单,直接使用CPU上HOST主桥引出的PCI总线,一般不会出现多级PCI总线。所以总线号也很好确定。

一条PCI总线的设备号由PCI设备的IDSEL信号与PCI总线地址线的连接关系确定,即每一个PCI插槽的总线号和设备号都是固定的。这是硬件工程师决定的,可以询问他们。

PCI功能号与PCI设备的具体设计相关。在一个PCI设备中最多有8个 功能设备,而且每一个功能设备都有各自的PCI配置空间,而在绝大数PCI设备中只有一个功能设备。HOST主桥使用寄存器号,访问PCI设备配置空间的某个寄存器。

在MPC83xx处理器的HOST主桥中,与PCI设备配置空间相关的寄存器由CFG_ADDR、CFG_DATA和INT_ACK寄存器组成。系统软件使用CFG_ADDR和CFG_DATA寄存器访问PCI设备的配置空间,而使用INT_ACK寄存器访问挂接在PCI总线上的中断控制器的中断向量。

MPC83xx处理器使用CFG_ADDR寄存器和CFG_DATA寄存器访问PCI设备的配置空间,其中用CFG_ADDR寄存器保存PCI设备的ID号,该寄存器的各个字段的详细说明如下所示:

Enable位:当该位为1时,HOST主桥能对PCI设备配置空间的访问,当HOST处理器对CFG_DATA寄存器进行访问时,HOST主桥将对这个寄存器的访问转换为PCI读写总线事务并发送到PCI总线上。

  • Bus Number字段:记录PCI设备所在的总线号。
  • Device Number字段:记录PCI设备的设备号。
  • Function Number字段:记录PCI设备的功能号。
  • Register Number字段:记录PCI设备的配置寄存器号。

对于MPC83xx系列处理器,在访问PCI设备的配置空间时,首先需要在CFG_ADDR寄存器中设置这个PCI设备对应的总线号、设备号、功能号和寄存器号,然后使能Enable位。之后当处理器对CFG_DATA寄存器进行读写访问时,HOST主桥将这个存储器读写访问转换为PCI配置读写请求,并发送到PCI总线上。如果Enable位没有使能,处理器对CFG_DATA的访问不过是一个普通的I/O访问,HOST主桥并不能将其转换成PCI配置读写请求。

VxWorks动态加载

使用动态加载目标模块的方式有很多好处,比如可以在不破坏原来的环境下增加调试定位功能,相当于给系统打“补丁”,不需要编译原来的代码(甚至可以不用原来的代码)而只需要关注正在调试的代码,这样能减少编译时间和减少映像的加载量。

实现目标模块的动态加载有很多种方法,如在主机环境的界面上通过在目标模块上单击鼠标右键,选择“Download 文件名”;也可以通过wShell和GDB命令行窗口实现。本文通过tshell下使用ld()、loadModule()、loadModuleAt()中一个函数来实现,当然在代码中也可以自如地调用它们。

ld命令是由用户接口子程序库usrLib提供的一个加载命令。使用ld的前提是在config.h中定义INCLUDE_LOADER。这样,在usrRoot()函数中就会自动调用加载模块初始化函数moduleLibInit();同时,根据CPU类型,自动决定目标模块的格式。如果CPU是MIPS、PPC、ARM、I80X86、COLDFIRE、SIMSPARCSOLARIS、SH等,加载的目标模块格式是elf类型,就会调用loadElfInit();如果CPU是I960、AM29XXX等,加载的目标模块的格式则是coff类型,就会调用loadCoffInit()函数。

在ARM和PPC下,Tornado编译器生成的.o或.out都是elf类型,打开目标文件都会看到文件头有ELF(45,4C,46)标记。这时可以通过ftp工具把它加载到文件系统(如使用copy命令加载到RAM盘)中,再调用ld()或loadModule()函数加载到内存中运行。

ld的函数原型是:MODULE_ID ld( int syms, BOOL noAbort, char *name )。参数syms决定目标文件的符号怎么处理:0,添加全局符号到系统符号表中;1,添加全局和局部符号到系统符号表中;-1,符号不添加到系统符号表中。一般选1,便于在shell下使用其中的符号。参数noAbort表明是否可以忽略加载期间出现的错误,为TRUE则忽略,FALSE则不忽略。name则为加载的文件名,包含文件路径。注意:ld是一条shell命令,也就是它是为在shell下调用而设计的一个函数,所以尽量不要用在代码内部,因为在之后的vxworks版本中直接调用ld可能会不支持。

loadModule的函数原型是:MODULE_ID loadModule( int fd, int loadFlag )。fd为文件描述符号,需要先打开文件获取fd;参数loadFlag含义有LOAD_NO_SYMBOLS(2)、LOAD_LOCAL_SYMBOLS(4)、LOAD_GLOBAL_SYMBOLS(8)、LOAD_ALL_SYMBOLS(0xC)三种。