随着通信技术、计算机技术的飞速发展,嵌入式实时操作系统越来越广泛地应用到无线通信、交通、工业控制、军事、航空航天、卫星通信等各个领域。由于这些领域对实时性、可靠性要求很高,从而使得实时操作系统迅速发展起来。其中VxWorks是目前公认的最出色的一种实时操作系统。VxWorks具有可裁减的微内核;高效的任务管理;优先级抢占和时间片轮转调度;准确的上下文切换;快速灵活的任务间通信等优点。它已成为实际的嵌入式实时操作系统的工业标准和军用标准[1]。嵌入式系统特别强调“量身定做”的原则,基于某一种特殊用途,可以针对这项用途开发出截然不同的一项系统,这就是所谓的客制化[2]。通常需要根据系统的功能、成本、尺寸及用户需求等方面来定制合适的硬件系统,这就要求用户开发自己的硬件驱动程序。驱动程序的开发是系统开发的重要组成部分,其性能、实时性、可靠性、指令的简练性关系着应用系统的性能和可靠性,所以驱动程序的开发显得至关重要。
本文针对以PCI9030为接口控制芯片的PMC-FPGA(PCI Mezzanine Card)转接卡的开发,论述了VxWorks下设备驱动程序结构及PMC-FPGA板卡驱动程序的实现。
1 VxWorks下I/O系统及驱动程序
1.1 I/O系统简介
VxWorks下I/O系统为各种设备提供一个简单、统一、独立的设备接口,VxWorks下I/O系统的独特设计使其比其他I/O系统更快、更灵活。这是实时系统的一个重要特征。[3]I/O系统的功能是将用户的I/O请求路由到合适的驱动程序中的对应函数。它通过一个文件描述符表来实现这一功能。VxWorks设备驱动程序基本上通过I/O系统进行访问,设备驱动程序被作为内核过程来实现,进一步提高了系统的实时性。I/O系统把设备作为特殊文件进行处理,提供了统一的管理、统一的界面和统一的使用方法,并把设备、文件、网络通信组织成为一致的层次抽象。图1描述了I/O系统与驱动程序间的层次关系。
VxWorks下I/O系统中三个主要的元素是:驱动程序、设备和文件[3]。文件是用户访问设备的统一接口;驱动程序是实现I/O系统所需的七个具体基本函数;设备是实际物理设备的抽象定义。
1.2 驱动程序的执行逻辑
VxWorks下I/O系统提供七个基本的I/O函数:creat( ), delete( ), open( ), close( ), read( ), write( )及ioctl(),并将用户的I/O请求路由到设备对应的子程序。这主要是通过找到驱动列表中对应的驱动号来实现。驱动列表如表1所示。
在驱动程序中通过调用iosDrvInstall()函数将驱动程序中的子程序(myOpen( ), myRead( )等)注册到系统的设备驱动列表中并返回一个驱动号,如表1中所示的驱动号3。
然后编写创建设备子程序,通常命名为xxCreate(),这要定义一个双向链表的DL_NODE结构和一个设备描述符的结构,结构中第一个变量是DEV_HDR类型,称为设备头。调用iosDevAdd()函数,将设备添加到设备列表中(见图2)。其中有管道设备、串口设备及PCI9030设备,pmcfpga是设备名称。
在iosLib.h()头文件中,对设备头DEV_HDR定义如下:
typedef struct
{ DL_NODE node; /* 设备表结点 */
short drvNum; /* 设备的驱动号 */
char * name; /* 设备名 */
} DEV_HDR;
此外还要定义一个称为设备描述符表的数据结构来将设备和驱动联系起来(见表2)。这个设备描述符表中除了包含设备头DEV_HDR、设备信息(包括设备号、厂商号、总线号、功能号、中断号等)外,还可包含其他一些与设备无关的变量,如文件偏移量、布尔代码设置的标志等。定义如下:
typedef struct PCI9030Device
{DEV_HDR;
PCI9030_INFO;
}PCI9030_DEVICE;
从表2中可以看出设备名是pmcfpga, 驱动号为3,文件描述符号是2。通过调用iosDrvInstall()将驱动程序添加到驱动程序表并返回一个驱动号,再调用iosDevAdd()将设备描述符添加到设备列表并用设备名字(drvName)和驱动程序号(drvNum)对DEV_HDR初始化。
驱动程序装载完毕后,用户执行open(“/ pmfpga”, 参数2,参数3),I/O系统根据文件名找到设备及驱动号,并通过驱动表找到对应的驱动子程序入口函数myOpen(),同时返回文件描述符号fd。以后就可根据fd执行其他函数的操作,如:close (fd)、read (fd, 参数2,参数3)等。图3给出了设备、驱动程序连接的逻辑示意图。
2 PCI9030设备的访问
2.1 PCI9030简介
本设计的PMC转接卡选用PLX公司的PCI9030作PCI总线的接口控制芯片,执行PMC总线标准,主要应用于目前较为流行的一些只有PMC接口的单板机上,例如:VMIC公司生产的VMIVME系列;MEN公司生产的PowerPC单板机等。在PMC转接卡上可以挂上FPGA、 DSP等数字处理能力较强的芯片以实现高速控制器,提高数字处理速度和运算速度,从而进一步满足系统实时性的要求。PMC转接卡内部逻辑结构如图4所示。
这里FPGA作为PCI9030接口芯片的外设挂到PCI局部总线上,主板上的CPU要从PMC接口通过PCI9030才能访问到外设,因此需要实现从PCI总线通过PCI9030到局部总线的访问,所以驱动程序的关键是实现PCI局部总线到PCI总线的地址映射。
2.2 访问PCI9030外设的实现
PCI9030有一个64字节的目标设备读FIFO和一个128字节的PCI目标写FIFO,用来实现PCI主设到局部总线的访问,并允许5个地址空间去访问局部地址空间,它们分别是space0、space1、space2、space3和Expansion ROM。每个PCI地址空间到局部地址空间的映射关系由三个寄存器来决定: Local Address Range(局部基地址范围)——LASxRR 及EROMRR; Local Base Address(局部基地址)——LASxBA及EROMBA; PCI Base Address(PCI基地址)——PCIBAR2、PCIBAR3、 PCIBAR4、 PCIBAR5及PCIERBAR[4]。每一个空间的映射是一一对应的,规定space0映射到PCI BASE2, space1映射到PCI BASE3,以此类推。图5为地址映射示意图。
PCI9030复位初始化时需定义一次地址空间的映射,具体实现方法是在E2PROM中配置好三个寄存器的值:①Range:指定PCI总线访问局部总线空间范围的PCI地址位。除去最低一个字节,左起第一个为1的数对应的位就是该空间范围。例如:配置Range=FFF00008,去掉第一个字节,左起第一个1在bit20, 所以空间范围是220=1MB。②Remap(Local Base Address):根据自己的硬件设计决定设备在局部总线的基地址,bit0必须置为1,表明允许对应的局部空间地址映射。③Descriptor:指定局部总线的特征,包括总线宽度、读延时、写延时的延时时钟数、是否预取等。
驱动程序的设备初始化子程序给PCI Base Address 寄存器写入全1,即FFFFFFFF,然后读回的寄存器的值就是CPU根据E2PROM中的配置分配的PCI的基地址范围值。在VxWorks中通过调用以下两个函数来实现:
pciConfigInLong(busNo,deviceNo,funcNo,PCI_CFG_BASE_ADDRESS_2,& baseAddress);
pciConfigOutLong(busNo,deviceNo,funcNo,PCI_CFG_BASE_ADDRESS_2,& baseAddress);
当需要访问某一设备时,只需要访问该设备占用的局部地址空间所对应的PCI地址空间即可,一般用PCI基地址加偏移量的方式访问,例如:PCI_READ(MEMBase2,0x04,buffer)。此外PCI9030 还提供了四个片选信号,以便对多个外设进行访问,这四个片选信号是CS0、CS1、CS2、CS3,对应四个片选基地址寄存器:CS0BASE, CS1BASE、CS2BASE、CS3BASE, 当片选基地址寄存器表达的基地址与某一局部空间基地址相同且设定的地址范围也相同时,这一片选信号上所挂的设备就落在该局部空间[4]。本次设计中所用的FPGA芯片使用了CS0片选信号,设计的CS0BASE基地址和范围落在space0,这样通过PCI BASE2 ADDRESS 就可访问到FPGA。
2.3 PCI9030的中断及控制寄存器
PCI9030提供了两个局部中断引脚LINTi1和LINTi2以及中断控制寄存器INTCSR(软中断)[7],用来触发PCI中断输入引脚INTA#输出有效信号,向CPU申请中断。同时PCI中断引脚寄存器PCIIPR必须在E2PROM中设置为1h, 表明通过INTA#申请中断。BIOS能够将INTA#路由到一个中断控制器的中断请求(IRQ)输入,为系统分配一个与标准的中断控制器8259的IRQ相应的中断号,并由BIOS将此中断号写到PCI中断线(PCI Interrupt Line)寄存器PCIILR中。因此在写驱动程序时,读取PCIILR中的值是将中断服务子程序ISR连接到中断控制器的关键。三种中断形式的设置可在中断控制寄存器INTCSR中配置。也可通过设置中断控制寄存器INTCSR[6]来禁止INTA#中断。
通过E2PROM编程器或PLX公司的PlxMon软件对E2PROM编程,可写入几个重要寄存器的值,其中包括配置寄存器(PCI配置首区[5])、局部配置寄存器等的值。系统上电后自动将这些数据从E2PROM传到PCI9030内部寄存器。在PCI配置寄存器00h中放置两个重要参数Device ID(设备ID)和Vendor ID(厂商ID), 它们是表明设备身份的关键,在VxWorks中利用这两个参数来调用如下函数:
pciFindDevice(PCI_VENDOR_ID, CI_DEVICE_ID, index,&busNo, &deviceNo, &funcNo) ;
找到设备并读取busNo(总线号)、deviceNo(设备号)、
funcNo(功能号)等参数后,再利用这三个参数找到PCI基地址和中断号,最后作地址映射,将基地址加到内存管理单元MMU中。这是PCI9030设备初始化的重要步骤。
3 VxWorks下的PCI9030驱动程序结构及流程
3.1驱动程序的框架
前面提到在VxWorks下有七个基本I/O函数,所以写PCI9030驱动程序时首先要写PCI9030的I/O接口子程序PCI9030Open()、PCI9030Close()、PCI9030Read()等。 此外还需要写三个重要的程序[3]:
PCI9030Drv()。该程序的主要工作是执行必要的硬件初始化;通过调用iosDrvInstall()函数装载驱动程序到I/O系统,连接I/O接口子程序PCI9030Open()、PCI9030Close()、PCI9030Read()等到I/O系统;连接中断等。
PCI9030Create()。该程序的主要工作是将设备添加到I/O系统,通过调用iosDevAdd()函数来实现。
中断服务子程序ISR。该程序完成写入中断响应后需要执行的操作。
3.2 程序流程图
图6和图7分别为设备驱动程序PCI9030Drv()的流程图及设备所需接口函数的框图。
3.3 部分程序代码
(1)sysPCI9030Init()子程序。
void sysPCI9030Init (void)
{设备初始化……
pciIntConnect(INUM_TO_IVEC(((int) btRes->irq)+INT_NUM_IRQ0),(VOIDFUNCPTR)myISR,IntParameter);
/*连接到中断服务子程序*/ }
(2)PCI9030Drv()程序,装载驱动程序到驱动表并连接I/O接口函数。
int PCI9030Drv()
{
int pci9030DrvNum; /*定义驱动号*/
pci9030DrvNum=iosDrvInstall((FUNCPTR)PCI9030open,0,
(FUNCPTR)PCI9030open, (FUNCPTR)PCI9030close,
(FUNCPTR)PCI9030read, (FUNCPTR)PCI9030write,0);
……
}
本文针对PMC-FPGA转接板的开发设计,研究了在VxWorks下的驱动程序的编写及PCI接口控制芯片PCI9030的应用,论述了VxWorks下PCI9030驱动程序的设计方法。由于VxWorks操作系统的微内核可裁减及高效的多任务管理方式,为提高系统的实时性作了很大贡献。又因VxWorks有丰富高效的接口函数,使编程更加简单方便。I/O系统将设备程序作为内核过程实现,实时性、可靠性都得到很大提高。在硬件设计中,采用FPGA可编程逻辑门阵列芯片,通过编程实现硬件逻辑运算,大大提高了运算速度,进一步提高了系统的实时性。这里设计的PMC-FPGA转接板也为高速控制器的设计提供了硬件条件,对控制系统中实时性、快速性的实现具有现实意义。