VxWorks PCI 网卡驱动开发

vxworks中vxbus使用入门

或许用过vxworks操作系统的人不多。但作为曾经的嵌入式操作系统老大,介绍一下还是非常有意义的。

开发步骤

1.       添加新模块(这里会告诉vxworks该模块的入口函数);

2.       填充vxbDevRegInfo,并通过vxbDevRegister进程注册驱动程序;

3.       通过向hcfDeviceList数组中添加记录,完成设备的注册;

4.       Vxworks启动是会实例化hcfDeviceList中的设备(不必人工干预);

5.       在vxbDevRegInfo中注册的几个初始化函数中添加实际的设备驱动代码;

vxBus模块添加

Vxworks中采模块化的方式管理各个功能单元,驱动也是由一个或多个模块组成。

VxBus是vxworks中的模块化机制。类似于linux中的module。通过vxBus我们可以方便的裁剪或添加新的模块。

模块的裁剪通常是在vxworks的开发环境workbench中进行的。建立一个工程后,会得到一个config文件,通过鼠标操作就可以实现模块的裁剪。

添加新模块则复杂一些,需要添加几个新的文件,但内容很简单,只是用来设置一些必须的信息。添加完成后,就可以像标准模块一样裁剪了。添加模块的详细过程可以再workbench中的vxbus的pdf文档中得到,说的很详细。

vxBus驱动注册过程

vxbus驱动的注册通过vxbDevRegister实现。其原型如下:

 

STATUS vxbDevRegister
(
    struct vxbDevRegInfo * pDevInfo /* per-bus recognition info */
)

显然,我们唯一要做的就是填充结构体pDevInfo,其类型定义如下:

struct vxbDevRegInfo

{

    struct vxbDevRegInfo * pNext;

    UINT32  devID;

    UINT32  busID;

    UINT32  vxbVersion;

    char    drvName[MAX_DRV_NAME_LEN+1];

    struct drvBusFuncs * pDrvBusFuncs;

    struct vxbDeviceMethod * pMethods;

    BOOL (*devProbe) ( struct vxbDev * pDevInfo);

    struct vxbParams * pParamDefaults;

};

多数不用讲,只说几个作用大的。

devProbe用于设备probe设备。检测当前是否有该类型设备。如果不需要进行probe,则可以设为NULL。

pNext用于总线级联。如一个挂在网卡上的网卡,其phy通过mii与mac相间接。我们要访问phy,则需要先通过pci总线访问mac,在通过mii总线访问phy。有点类似先做汽车到县城,再转驴车到村子。这里的pNext就是告诉你下面该转驴车了。如果没有级联,则设为NULL。

pMethods用于提供了总线的各种方法,但实际上通常只提供该总线特有方法。因为通用方法,如open、read等,一般都是通过io层的system call调用的,他们需要单独注册。

pDrvBusFuncs提供了设备初始化需要的几个接口,一共有三个:

struct drvBusFuncs
{
    void (*devInstanceInit) (struct vxbDev *);

    void (*devInstanceInit2) (struct vxbDev *);

    void (*devInstanceConnect) (struct vxbDev *);
};

devInstanceInit在kernel初始化前被调用,如果这个设备或它的一部分是kernel要用到的,就要放在这里。

devInstanceInit2在kernel初始化后被调用,没有什么特别要求的话,初始化最好都放在这里。当然如果该设备特别重要,其他设备需要调用它,一般也会放到devInstanceInit中,因为各个设备之间的调用devInstanceInit时不保证前后顺序。

devInstanceConnect用于设备的连接,通常可以不使用,但如果它依赖于其他设备,则可以把启动代码放在这里。

如果实在不需要,可以都置为NULL。

到这里为止,该驱动的框架就搭起来了。用启动image后,执行vxBusShow命令,可以看到新的设备驱动已经被加进去了。但我们的设备驱动还没有用。因为还没有与具体设备绑定,也就是实例化。单纯的驱动只是一堆代码,单纯的设备只是一堆废铁(废硅?)。驱动与设备的结合才是我们需要的。

vxBus设备实例化

hcfDeviceList数组管理着vxworks中的所有设备。我们要做的就是添加条记录。注意这里的名字实际上是设备驱动的名字,必须与之前pDevInfo中的驱动名一致。

Vxworks正是通过两个字符串的匹配,实现设备与驱动的匹配的。看起来用字符串匹配似乎效率有些低,但却避免了linux中因为主设备号只有255个而产生的尴尬。也许是一种更好的方法。

hcfDeviceList的最后一个成员变量pResource记录着一些该设备的特定信息。如串口波特率等。在代码中可以通过devResourceGet()获得。

系统启动后会通过hcfDeviceList初始化各个设备,此时会依次调用设备指向的驱动中的各个函数,如devInstanceInit。

完成这些配置后,再启动image,执行vxBusShow命令,最后一组实例(instance)信息中可以看到多了一个我们创建的实例。

最后一步

对于vxbus驱动来说,最后一步就是在instance、connect等函数中添加实际的驱动代码,这与一个普通的vxworks驱动没有区别。

总的来说,vxbus只是给了一个驱动框架,使得我们驱动程序的添加更统一。

 本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接。内容可任意使用,但对因使用该内容引起的后果不做任何保证。

作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net
-----------------------------------------------------------------------------------------

基于VxBus的设备驱动开发

         VxBus是风河公司新的设备驱动程序架构,是VxWorks新增的特性,它是在VxWorks6.2及以后版本被增加到VxWorks中的。本文结合基于PCI2040数据采集卡驱动的开发过程,分析了VxBus架构下驱动的设计实现。

VxBus简介

       VxBus是指在VxWorks中用于支持设备驱动的特有的架构,这种架构包含对minimal BSP的支持。它包括以下功能:

  • 允许设备驱动匹配对应设备;
  • 提供驱动程序访问硬件的机制;
  • 软件其他部分访问设备功能;
  • 在VxWorks系统中,实现设备驱动的模块化。

        VxBus在总线控制器驱动程序服务的支持下,能在总线上发现设备,并执行一些初始化工作,使驱动与硬件设备之间正常的通讯。

VxWorks VxBus Demonstration

        图1是VxBus 在整个系统中的位置示意图。从图1中可以看到,VxBus起到了辅助总线的作用,提供了对总线控制驱动的支持。

        在VxWorks6.2版本发布前,设备驱动并不能被集成到VxWorks工程配置当中,为了添加或移出设备驱动,需要有丰富的BSP和驱动开发相关的知识。作为VxWorks系统组件的一部分,VxBus消除了上面遇到的一些难题,各种驱动和支持组件的添加与删除完全可以在Workbench工程中进行,不需要BSP和驱动相关的知识,也不会在添加或者删除驱动时增加管理VxWorks工程的额外工作。

        vxBus下对设备管理做了更为详细的划分,简单说来,硬件称为device,软件叫做driver。如果一个device出现在硬件列表中,启动时需要到driver的队列中去找相应的driver,如果找到,二者结合成一个instance,否则在vxBusShow里可以看到一个orphan。使用vxBusShow可以比较清晰的看到driver列表和device列表以及orphan列表。

        如果需要增加一个设备,首先要修改的是hcfDeviceList,这是hwconf.c中的一个数组,这个数组中列有本系统中所有需要初始化的硬件设备。例如:

  HCF_DEVICE hcfDeviceList[] = {
  #ifdef DRV_SIO_ns16550
      { "ns16550", 0, VXB_BUSID_PLB, 0, ns1655x1Num, ns1655x1Resources },
      { "ns16550", 1, VXB_BUSID_PLB, 0, ns1655x2Num, ns1655x2Resources },
  #endif

        第二个ns16550是新加的。在参数中指明了它的名字“ns16550”,初始化的时候根据这个名字去搜索驱动。它的unit是1,区别与前面的0,初始化结束以后,会在设备列表中看到这个unit number。ns1655x2Resources是前面定义的一组资源,跟ns1655x1Resources类似,只是regBase和irq按照实际情况做区分。ns1655x2Num是ns1655x2Resources的长度。

        对于单核CPU,配置到这里就结束了。如果是多核,还需要修改一下sysDeviceFilter,这个函数决定一个设备在哪个核上初始化。如果还有hypervisor,还需要修改wrhvConfig.xml和vxworksX.xml,将特定的终端放开到指定的核。

硬件介绍

        TI公司推出的PCI2040是一款用于实现PCI局部总线与DSP之间无缝链接的专用芯片。在VxWorks实时操作系统环境下实现主机与DSP的通讯,系统利用PCI2040实现TMS320VC5410与主机的通讯。TMS320VC5410的MCBSP0与TLC2548 连接,实现8路12位A/D数据的采集。TMS320VC5410将采集到的数据通过PCI2040传输到主机上,数据在主机上得到进一步的处理。硬件连接框图如图2所示。

VxWorks VxBus hardware connection

驱动开发
设备驱动初始化

        设备的初始化,包含在BSP的初始化过程中,主要分三个阶段,如图3所示。

内核预初始化阶段

        在VxWorks内核预初始化早期,BSP的sysHwInit( )函数被执行[4],在这个函数中,设备驱动初始化工作第一步被执行。sysHwInit( )函数执行一些早期的初始化,调用hardWareInterFaceInit( )函数,执行初始化硬件内存分配机制,限制为设备驱动分配内存,这个函数接着调用hardWareInterFaceBusInit( ),在hardWareInterFaceBusInit( )函数中完成所有设备驱动和模块的注册工作。PCI2040的注册函数是vxbPci2040Register()。vxbPci2040Register()通过数据结构,向系统注册一些设备初始化函数。其中涉及到三个数据结构:

  LOCAL struct drvBusFuncs PciFuncs =
  {
      Pci2040InstInit,    /* devInstanceInit */
      Pci2040InstInit2,    /* devInstanceInit2 */
      Pci204InstConnect    /* devConnect */
  }

在这个结构中,包含了初始化阶段要调用的函数。下面的初始化过程会用到这些函数。

  LOCAL struct vxbDeviceMethod Pci2040Methods[] =
  {
      DEVMETHOD(ReadHPID,    PCI2040ReadHPID),
      DEVMETHOD(WriteHPID,    PCI2040WriteHPID),
      DEVMETHOD(ReadHPIA,    PCI2040ReadHPIA),
      DEVMETHOD(WriteHPIA,    PCI2040WriteHPIA),
      DEVMETHOD(ReadHPIC,    PCI2040ReadHPIC),
      DEVMETHOD(WriteHPIC,    PCI2040WriteHPIC),
      DEVMETHOD(ReadCSR,    PCI2040ReadCSR),
      DEVMETHOD(WriteCSR,    PCI2040WriteCSR),
      { 0, 0 }
  }
这个结构提供了应用软件操作硬件的一些函数及方法。
 LOCAL struct vxbPciRegister Pci2040DevPciRegistration =
 {
      {
          NULL,                        /* pNext */
          VXB_DEVID_DEVICE,        /* devID */
          VXB_BUSID_PCI,            /* busID = PCI */
          VXB_VER_4_0_0,                /* vxbVersion */
          LNPCI_NAME,                    /* drvName */
          &Pci2040Funcs,            /* 总线驱动函数*/
          Pci2040Methods,            /* 设备方法结构 */
          Pci2040Probe,                    /* 设备探测函数 */
          Pci2040ParamDefaults        /* 参数*/
      },
      NELEMENTS(PciPci204DevIDList),
      PciPci204DevIDList                /*设备资源列表*/
 };

        最后这个结构在vxbPci2040Registe()中被使用。这个结构包括几个驱动的初始化入口,其中Pci2040Probe()是PCI2040采集卡的硬件探测函数,该函数在VxBus初始化过程中检测采集卡的数量,当检测到采集卡时,将采集卡与驱动结合,形成设备的一个实例,以便应用程序使用。Pci204InstanceInit( )函数在VxBus初始化的第一阶段被调用, Pci204InstanceInit( )函数只是简单地确保设备的中断被禁止。

        当所有驱动在VxWorks注册之后,hardWareInterFaceBusInit( )和hardWareInterFaceInit( ) 函数返回,sysHwInit( ) 完成非VxBus 驱动的初始化并返回。sysHwInit( ) 函数返回后,VxWorks内核被初始化。

应用程序初始化驱动部分

        在devInstanceInit2( )函数最后,创建用户的运行任务,并完成设备驱动的初始化。在这个阶段,Pci2040InstanceConnect( )函数被调用,完成最后的初始化工作,在这个函数中,主要是建立中断与中断服务程序的连接。

驱动程序的配置

        采用VxBus驱动的一个主要优点是:设备的驱动程序可以被看成VxWorks 系统的一个组件,通过集成的Workbench开发环境来配置设备驱动。PCI2040数据采集卡需要有以下文件:

  • 一个驱动源文件PCI2040.c,执行驱动运行逻辑,包括PCI2040驱动的实现代码。
  • 一个组件描述文件PCI2040.cdf,允许集成驱动到VxWorks开发工具Workbench当中。
  • 一个PCI2040.dc文件,提供驱动注册函数原型。
  • 一个PCI2040.dr文件,提供一个调用注册函数的C语言代码段。
  • 一个readme文件 ,提供版本信息。
  • 一个makefile 文件,提供建立驱动的编译规则。
应用程序与驱动的通信

        为了使设备和驱动能够在VxWorks系统中使用,让应用程序、中间件、VxWorks内核模块访问设备,执行一些操作,最基本的方法是在VxWorks中采用VxBus方法来实现硬件设备的访问。VxBus方法是在驱动中公开一个入口,使VxBus中API函数可以调用这些入口函数。在PCI2040初始化阶段,Pci2040Methods结构中注册的函数就是在驱动中公开的函数,用于对PCI2040的操作。

例如,通过PCI2040 完成对DSP数据寄存器的访问

struct vxbDriverControl ctrl;
vxbDevMethodRun(DEVMETHOD_CALL(ReadHPID),&ctrl);

vxbDevMethodRun( )函数够被用于调用一个指定的驱动方法,这个函数反复查找所有的实例,并检查每一个,看是否有指定公开申明的方法,如果实例有指定的方法,vxbDevMethodRun( )调用方法函数。

为了避免重复遍历在系统上的所有实例,可以用 vxbDevMethodGet( )函数找出驱动函数相对应的驱动方法 ,然后通过下面代码完成函数调用。

STATUS (*methodFunc)(VXB_DEVICE_ID devID, void * pArg);
methodFunc = bDevMethodGet(devID,DEVMETHOD_CALL(ReadHPID));
if(methodFunc != NULL )
    (*methodFunc)(devID, pArg);

在PCI2040的数据采集卡中,通常是DSP在采集完数据后,通过中断通知主机,去读取数据。下面是中断服务相关代码。

void PCI2040Isr()
{ ……
  temp=*(PCI2040. instID.pRegister+0x4); //读中断寄存器
  if((tempr&0x1)!=0)               //检查是否是该实例中断
  { 
        *(PCI2040. instID.pRegister +0x4)=0x1;
      temp= *(PCI2040. instIDpDspHpicRegister);
      *(PCI2040. instIDpDspHpicRegister)=temp|0x0808;
      //通知DSP,清除HINT中断
      semGive(semForPci2040Int);
  }
}

采用基于VxBus架构来开发PCI2040数据采集卡的驱动,通过扩展文件实现驱动的配置。与简单的非VxBus驱动相比,显然增加了工作量,然而对于基于多个BSP设备的复杂的驱动,VxBus驱动是优于非VxBus驱动的。通过实际运用证明,所开发采集卡的驱动能够稳定运行,并且能很方便地将该驱动移植到其他的系统。

-----------------------------------------------------------------------------------------

基于VxWorks的VxBus字符设备驱动

VxBus是指在 VxWorks 中用于支持设备驱动的特有的架构,这种架构包含对minimal BSP的支持。它包括以下功能:

  • 允许设备驱动匹配对应设备;
  • 提供驱动程序访问硬件的机制;
  • 软件其他部分访问设备功能;
  • 在VxWorks系统中,实现设备驱动的模块化。

VxBus是Vxworks的模块化机制,类似于linux中的module。通过VxBus可以对模块方便的裁剪或者添加。VxBus 在总线控制器驱动程序服务的支持下,能在总线上发现设备,并执行一些初始化工作,使驱动与硬件设备之间正常的通讯。

vxBus下对设备管理做了更为详细的划分,简单说来,硬件称为device,软件叫做driver。如果一个device出现在硬件列表中,启动时需要到driver的队列中去找相应的driver,如果找到,二者结合成一个instance,否则在vxBusShow里可以看到一个orphan。使用vxBusShow可以比较清晰的看到driver列表和device列表以及orphan列表。

相关参数
设备注册:vxbDevRegister(structure vxbDevRegInfo *pDevInfo)
显然,我们唯一要做的就是填充vxbDevRegIndo *pDevInfo结构体,其定义如下
struct vxbDevRegInfo

{

        struct vxbDevRegInfo * pNext;

        UINT32 devID;
        UINT32 busID;
        UINT32 vxbVersion;
        char drvName[MAX_DRV_NAME_LEN+1];
        struct drvBusFuncs * pDrvBusFuncs;
        struct vxbDeviceMethod * pMethods;
        BOOL (*devProbe) ( struct vxbDev * pDevInfo);

        struct vxbParams * pParamDefaults;

};

参数解释:

pNext
pNext用于总线级联。如一个挂在网卡上的网卡,其phy通过mii与mac相间接。我们要访问phy,则需要先通过pci总线访问mac,再通过mii总线访问phy。有点类似先做汽车到县城,再转驴车到村子。这里的pNext就是告诉你下面该转驴车了。如果没有级联,则设为NULL。
pMethods
pMethods用于提供了总线的各种方法,但实际上通常只提供该总线特有方法。因为通用方法,如open、read等,一般都是通过io层的system call调用的,他们需要单独注册。
pDrvBusFuncs
pDrvBusFuncs 提供了设备初始化需要的几个接口,一共有三个:
struct drvBusFuncs
struct drvBusFuncs
{

  void (*devInstanceInit) (struct vxbDev *);

       ​​​​​​​void (*devInstanceInit2) (struct vxbDev *);

  void (*devInstanceConnect) (struct vxbDev *);

};

devInstanceInit在kernel初始化前被调用,如果这个设备或它的一部分是kernel要用到的,就要放在这里。devInstanceInit2在kernel初始化后被调用,没有什么特别要求的话,初始化最好都放在这里。当然如果该设备特别重要,其他设备需要调用它,一般也会放到devInstanceInit中,因为各个设备之间的调用devInstanceInit时不保证前后顺序。devInstanceConnect用于设备的连接,通常可以不使用,但如果它依赖于其他设备,则可以把启动代码放在这里。如果实在不需要,可以都置为NULL。

系统接口函数
        对于字符设备驱动来说,需要实现的仅为I/O系统所需要的接口,即 create, open, read, write, close, ioctl, delete 7个接口函数, I/O系统在与这些接口通信的时候都有规定的参数,其中最重要的就是设备的数据结构。这个数据结构会在设备打开时返回给系统,用于I/O系统与驱动之间的数据交换。
        一个典型的设备的数据结构定义为:

typedef struct
{
    DEV_HDR devHdr;
    int ix; /设备驱动号/
    SEL_WAKEUP_LIST selWakeupList;
    BOOL Opened;
    Bool ReadyToRead;
    Bool ReadyToWrite;
}xxx_DEV

/DEV_HDR 是用于创建设备链表的系统定义结构,如下所示/
typedef struvcr /设备头数据结构/

{
    DL_NODE node; /设备链表节点/
    short drvNum; /设备所对应的驱动索引号/
    cha *name; /设备名称/
}



用链表将设备管理起来,I/O系统只需要完成对链表的增删改查即可以完成对设备的添加和修改

heloWorld驱动实例
驱动程序的配置
  采用VxBus驱动的一个主要优点是:设备的驱动程序可以被看成VxWorks 系统的一个组件,通过集成的Workbench开发环境来配置设备驱动。PCI1304数据采集卡需要有以下文件:

  • 一个驱动源文件vxb335xHello.c,执行驱动运行逻辑,包括Hello驱动的实现代码。
  • 一个组件描述文件vxb335xHello.cdf,允许集成驱动到VxWorks开发工具Workbench当中。
  • 一个vxb335xHello.dc文件,提供驱动注册函数原型。
  • 一个vxb335xHello.dr文件,提供一个调用注册函数的C语言代码段。
  • 一个readme文件 ,提供版本信息。
  • 一个makefile 文件,提供建立驱动的编译规则。

helloWorld驱动实例
vxb335xHello.c

 
   
//@file vxb335xHello.c
//@brief GPIO控制按键驱动
/******************************************************************************

** includes

******************************************************************************/

#include <vxWorks.h>
#include <stdio.h> /* for printf() */
#include <logLib.h> /* for logMsg() */
#include <string.h> /* for strcmp() */
#include <vxBusLib.h> /* for VXB_DEVICE_ID */
#include <hwif/vxbus/vxBus.h>
#include <hwif/util/hwMemLib.h> /* for hwMemAlloc() */
#include <hwif/vxbus/hwConf.h> /* for devResourceGet() and* hcfDeviceGet()*/
#include <driverControl.h> /* for struct vxbDriverControl */
#include "../pmcConfig.h"
#include "vxb335xHello.h"

//extern void getUnixTime(UNIX_CLOCK_STRUCT *time);
/*********************************************************************************
** GLOBAL VARIABLE DEFINITIONS
*********************************************************************************/
LOCAL int g_helDrvNum = 0; /* 驱动程序索引号 */
//LOCAL SEM_ID g_semLedM; /* gpioLed互斥信号量 */


/*********************************************************************************
** MACRO DEFINITIONS
*********************************************************************************/

#define HELLO_DEVICE_NAME "/hello"

/*********************************************************************************
** FUNCTION DECLARATIONS
*********************************************************************************/
LOCAL void helInstInit(VXB_DEVICE_ID pInst);
LOCAL void helInstInit2(VXB_DEVICE_ID pInst);
LOCAL void helInstConnect(VXB_DEVICE_ID pInst);

//void helAm335xShow( VXB_DEVICE_ID pDev);
LOCAL int helDrvOpen(DEV_HDR *pHelDevHdr, int option, int flags);
LOCAL int helDrvClose(int helDevId);
LOCAL STATUS helDrvIoctl(int helDevId, int cmd, int arg);
LOCAL STATUS helWrite(int helDevId, int *pBuf, int len);
LOCAL STATUS helRead(int helDevId, int *pBuf, int len);

/*********************************************************************************
** VxBus 驱动框架
*********************************************************************************/
/* VXB初始化设备调用程序 */
LOCAL struct drvBusFuncs helFuncs =
{
	helInstInit, /* devInstanceInit */
	helInstInit2, /* devInstanceInit2 */
	helInstConnect /* devConnect */
};


LOCAL struct vxbDeviceMethod helMethods[ ] =
{
	{ 0, 0}
};

/*diver rigister info*/
LOCAL struct vxbDevRegInfo helDevRegistration =
{
	NULL, /* pNext */
	VXB_DEVID_DEVICE, /* devID */
	VXB_BUSID_PLB, /* busID = PLB */
	VXB_VER_5_0_0, /* vxbVersion */
	"hello", /* drvName */
	&helFuncs, /* pDrvBusFuncs */
	helMethods, /* pMethods */
	NULL, /* devProbe */
	NULL /* pParamDefaults */
};

/*********************************************************************************
** DRIVER FUNCTIONS
*********************************************************************************/



/*******************************************************************************
* gpioLedRegister - register GpioLed driver
* This routine registers the GpioLed driver and device recognition
* data with the vxBus subsystem.
*/

void am335xHelRegister(void)
{
	vxbDevRegister((struct vxbDevRegInfo *)&helDevRegistration);
}


/*******************************************************************************
*
* GpioLedInstInit - initialize GpioLed device
* This is the wrsample initialization routine
*/


LOCAL void helInstInit(VXB_DEVICE_ID pInst)
{
	return;
}


/*******************************************************************************
*
* GpioLedInstInit2 - initialize GpioLed device
* This is seconde phase initialize routine for VxBus driver.
*/

LOCAL void helInstInit2(VXB_DEVICE_ID pInst)
{
	HEL_DEV * pDrvCtrl;

	/* to store the HCF device */
    HCF_DEVICE * pHcf = NULL; 
 
    /* check for vaild parameter */
    if (pInst == NULL)
        return;
 
    /* allocate the memory for the structure */
    pDrvCtrl = (HEL_DEV *)malloc(sizeof(HEL_DEV));
 
    /* check if memory allocation is successful */
    if (pDrvCtrl == NULL)
        return;  
 
    /* get the HCF device from vxBus device structure */
    pHcf = hcfDeviceGet (pInst);
 
    /* if pHcf is NULL, no device is present */
    if (pHcf == NULL)
        return;
 
    /* per-device init */
    pInst->pDrvCtrl = pDrvCtrl;
 }
 
/*******************************************************************************
* gpioInstConnect - VxBus connect phase routine for Gpio driver
* This is connect phase routine.
*/  
LOCAL void helInstConnect(VXB_DEVICE_ID pInst)
{
    HEL_DEV * pDrvCtrl;
 
    pDrvCtrl = pInst->pDrvCtrl;
 
    logMsg("debug:helInstConnect entered.\n",0,0,0,0,0,0);
    if(pDrvCtrl == NULL)
    {
		// GPIO_DBG_MSG(2,"HelInstConnect: pDrvCtrl is NULL, pDev is 0x%x\n", pInst,1,2,3,4,5);
        return;
    }
  
    /*注册驱动*/
    if((g_helDrvNum = iosDrvInstall( helDrvOpen, (FUNCPTR) NULL, helDrvOpen,
                                    (FUNCPTR) helDrvClose, helRead, helWrite, helDrvIoctl )) < 0)
    {
        logMsg("debug:iosDrvInstall failed.\n",0,0,0,0,0,0);
        return;
    }
  
    /* 添加设备 */
    if(iosDevAdd(&pDrvCtrl->devHdr, HELLO_DEVICE_NAME, g_helDrvNum))
    {
        free((char *)pDrvCtrl);
        return ;
    }
 
    return;   
}
  
LOCAL int helDrvOpen(DEV_HDR *pHelDevHdr, int option, int flags)
{
    HEL_DEV *pHelDev = (HEL_DEV *)pHelDevHdr;
    if(pHelDev == NULL)
    {
        return ERROR;
    }
 
    return ((int)pHelDevHdr);
}
 
LOCAL int helDrvClose(int helDevId)
{
    HEL_DEV *pHelDev = (HEL_DEV *)helDevId;
    if(pHelDev == NULL)
    {
        return ERROR;
    }
 
    return((int)helDevId);
}
 
LOCAL STATUS helRead(int  helDevId, int  *pBuf, int  len)
{
    return OK;
}
 
LOCAL STATUS helWrite(int helDevId, int *pBuf, int len)
{
    return OK;
}
  
LOCAL STATUS helDrvIoctl(int helDevId, int cmd, int arg)
{
    switch((HEL_CTL)cmd)
    {
        case HEL_WRITE:    
            logMsg("ioctl write in kernel.\n",0,0,0,0,0,0);
            break;
        case HEL_READ:
            logMsg("ioctl read in kernel.\n",0,0,0,0,0,0);
            break;
        default:
            return ERROR;
    }
 
    return OK; 
}

vxb335xHello.dc文件

IMPORT void am335xHelRegister(void);

vxb335xHello.dr文件

#ifdef DRV_AM335X_HEL
    am335xHelRegister();
#endif /* DRV_AM335X_HEL */

hwconfig.c中添加设备列表
完成上述驱动之后,在hwconfig.c中添加设备列表以及硬件信息
如果需要增加一个设备,需要在hwconfig.c中的设备列表即hcfDeviceList数组添加设备信息,这个数组中列有本系统中所有需要初始化的硬件设备。例如:

HCF_DEVICE hcfDeviceList[] = {

#ifdef DRV_AM335X_HEL

    { "hello", 0, VXB_BUSID_PLB, 0, vxbHelNum, vxbHelResources},

#endif

​​​​​​​​​​​​​​

在参数中指明了它的名字“hello”,初始化的时候根据这个名字去搜索驱动。它的unit是0,初始化结束以后,会在设备列表中看到这个unit number。vxbHelResources是前面定义的一组资源。 对于单核CPU,配置到这里就结束了。如果是多核,还需要修改一下sysDeviceFilter,这个函数决定一个设备在哪个核上初始化。如果还有hypervisor,还需要修改wrhvConfig.xml和vxworksX.xml,将特定的终端放开到指定的核。

我们这里只需要添加设备列表和硬件信息就可以了。如下

/* Hello驱动硬件信息*/
#ifdef DRV_AM335X_HEL
const struct hcfResource vxbHelResources[] = {
    { "regBase",        HCF_RES_INT, { (void *)0} },
};
#define vxbHelNum NELEMENTS(vxbHelResources)
#endif
 
/*添加设备对设备链表*/
#ifdef DRV_AM335X_HEL
    { "hello",           0, VXB_BUSID_PLB, 0, vxbHelNum,    vxbHelResources},
#endif

VxBus的驱动相比 Linux 不需要虚拟内存物理内存的映射,所以实现起来简单多了。

参考:VxWorks的VxBus驱动_vxbdev-CSDN博客

--------------------------------------------------------------------------------------------------

VxWorks  VxBus驱动程序的组织结构

        VxWorks下的vxbus驱动程序最重要的部分就是驱动程序源代码文件,源文件描述了设备如何和VxBus、VxWorks OS交互。但是,VxWorks 设备驱动程序还需要另外一些文件,这些附加文件能够帮助你将自己编写的驱动集成到VxWorks编译环境中去,这也是发布驱动程序最重要的一步。本节主要讨论如何在源码树中找到相关的驱动程序文件和其他附加文件。最后还说明驱动程序的各个部分是如何安装在VxWorks OS中的。

        在开发驱动程序之前,了解驱动程序文件在VxWorks源码树中的位置是非常重要的,驱动程序文件主要分布在源码树中的3个不同位置。

  • installDir/vxworks-6.x/target/3rdparty 第三方提供的基于VxBus驱动模型的驱动,它们一般都做为插件安装到现有的VxWorks开发环境中
  • installDir/vxworks-6.x/target/src/hwif 风河官方提供的基于VxBus驱动模型的驱动程序,它们一般都作为标准产品的一部分,或者作为补丁来升级。
  • installDir/vxworks-6.x/target/src/drv 风河官方提供的基于传统模型的驱动程序,和VxBus不兼容。
风河官方的驱动程序

        根据驱动程序的类型,installDir/vxworks-6.x/target/src/hwif目录下的驱动被组织成不同的子目录,例如,定时器的驱动程序在目录installDir/vxworks-6.x/target/src/hwif/timer

第三方的驱动程序

        第三方驱动程序的组织方式允许驱动程序开发厂商和开发者创建第三方驱动程序,不需要担心不同厂商的文件之间的命名空间冲突。每一个想提供VxWorks驱动程序的厂商必须在3rdparty目录创建自己的子目录,比如说,Acme公司计划为vxworks开发第三方设备驱动程序,那么就必须在3rdparty目录创建自己的目录installDir/vxworks-6.x/target/3rdparty/acme,在这个目录下,不同类型的驱动程序又组织成不同目录,跟hwif目录一样。

驱动程序文件例子:wrsample

风河公司提供的VxBus的驱动程序例子位于目录:installDir/vxworks-6.x/target/3rdparty/windriver/wrsample,这些文件可以被当做模板来帮助你开发第三方驱动程序,具体信息请参考wrsample目录下的README文件。

其他必须的文件

尽管一个驱动程序可以包括很多文件,比如多个源文件和多个头文件,但是一个标准的VxWorks驱动程序有一个最小的文件集,对于大多数vxworks驱动程序最少要求6个文件:

  • 驱动程序源文件 实现驱动程序控制逻辑
  • 组件描述文件(CDF) 主要用于将驱动程序集成到VxWorks编译环境中,以便于配置
  • 一个driverName.dc文件 提供驱动注册例程的原型
  • 一个driverName.dr文件 提供一小段调用驱动注册例程的代码
  • README文件 提供版本信息
  • Makefile文件 提供编译规则

         一般情况下,CDF文件,dc文件,dr文件都被认为是驱动程序的配置文件,下面详细介绍这些文件。

驱动程序源文件

        驱动程序源文件包含了驱动程序功能的实现逻辑,它们被放在目录installDir/vxworks-6.x/target/src/hwif, 第三方的被放在目录installDir/vxworks-6.x/target/3rdparty。很多VxWorks设备驱动程序只包含一个源文件,一个驱动程序可以包含一个或者几个可选的头文件;但是驱动程序可以包含多个源文件,但是此时必须在Makefile里面提供各个模块的依赖规则。下面以文件vxbCn3xxxTimer.c来说明VxWorks驱动程序的结构。

        设备驱动程序的第一部分是一个描述VxBus初始化阶段要调用的例程的结构:

/* data structures used by the driver to register itself  
* with Vxworks  
*/  
/* drvBusFuncs provides a set of entry points into the  
* driver that are called during various phases of the  
* boot process. Drivers can choose to implement 1 or  
* more of these entry point, according to the needs of  
* the driver during its initialization phases.  
*/  
LOCAL struct drvBusFuncs cn3xxxTimerDrvFuncs =  
{  
    cn3xxxTimerInstInit, /* devInstanceInit */  
    cn3xxxTimerInstInit2, /* devInstanceInit2 */  
    cn3xxxTimerInstConnect /* devConnect */  
};

        接着就是描述驱动程序所支持的驱动方法的数据结构(每一种类别的驱动程序都必须实现该类的驱动方法):

/* cn3xxxTimerDrv_methods provides the list of driver  
* methods that this driver supports. For each driver  
* class supported by Wind River, one or more methods  
* are expected to be defined for the driver. For  
* timer driver class, the 'vxbTimerFuncGet' method  
* is required to be supported.  
*/  
LOCAL struct vxbDeviceMethod cn3xxxTimerDrv_methods[] =  
{  
    DEVMETHOD(vxbTimerFuncGet, cn3xxxTimerFuncGet),  
    {0,NULL}  
}; 

跟着就是描述该驱动程序需要的注册信息的结构:

/* The cnxxxTimerDrvRegistration structure provides a  
* description of the driver to VxWorks, so that VxWorks  
* can connect this driver to appropriate hardware during  
* the boot process.  
*/  
LOCAL struct vxbDevRegInfo cn3xxxTimerDrvRegistration =  
{  
    NULL, /* reserved for VxBus use */  
    VXB_DEVID_DEVICE, /* devID */  
    VXB_BUSID_PLB, /* busID = PLB */  
    VXB_VER_4_0_0, /* vxbVersion */  
    "cn3xxxTimerDev", /* drvName */  
    &cn3xxxTimerDrvFuncs, /* pDrvBusFuncs */  
    NULL /* pMethods */  
    NULL /* devProbe */  
}; 

在注册信息后面,驱动程序必须提供一个例程来向VxBus注册,表明该驱动程序的存在:

/* The vxbCn3xxxTimerDrvRegister function contains the  
* first instructions of the device driver that are  
* ever executed within a VxWorks system. This function  
* registers the driver with VxBus by providing pointers  
* to the data structures listed previously. Once this  
* step is complete, VxWorks is able to associate this  
* driver with appropriate hardware within the system  
* to form an instance.  
*/  
void vxbCn3xxxTimerDrvRegister (void)  
{  
    vxbDevRegister (&cn3xxxTimerDrvRegistration);  
} 

        由于驱动程序注册方法被当做是驱动程序的第一个入口点,VxWorks必须被配置成:当该驱动程序向VxBus注册时,VxWorks知道调用该入口点。为了做到这点,VxWorks使用了之前提到了那几个驱动配置文件:CDF文件,dc文件,dr文件。

注意: VxBus和VxWorks要求驱动程序的注册方法必须是全局的。大多数驱动程序并不需要其他的全局符号,因此都可以声明成LOCAL。

​​​​​​​

CDF文件

        该文件的全称是:component description File,组件描述文件。根据VxBus标准开发的VxWorks设备驱动程序都被编译成一个单独的模块,可以使用VxWorks配置工具非常轻松地将驱动程序配置进BSP中。但是,你必须为你的设备驱动程序创建一个VxWorks组件。

        一个组件是一个基本的功能单元,它可以单独配置进入VxWorks内核镜像中。为了能够单独添加和删除设备驱动程序到VxWorks中,驱动程序必须能够被VxWorks配置工具识别成individual 组件。为了让驱动程序能够在Workbench或者vxprj中是可以配置的,你必须创建CDF文件,CDF文件提供VxWorks配置工具所需要的信息。针对风河公司发布的设备驱动程序,其对应的CDF文件位于以下目录: 

        installDir/vxworks-6.x/target/config/comps/vxWorks

        在风河提供的驱动程序中,一个CDF文件可能包含着描述多个设备驱动程序的信息,对于第三方驱动,其CDF文件路径是在驱动程序目录下。

注意:内核配置工具并不自动搜索installDir/vxworks-6.x/target/3rdparty/ directories下的文件,为了让内核配置工具能够读取第三方驱动的CDF文件,必须手工将CDF文件拷贝到以下目录:installDir/vxworks-6.x/target/config/comps/vxWorks

        为新驱动编写CDF文件之前,首先拷贝一个标准的CDF文件到你的驱动程序目录,然后根据你的驱动程序来修改CDF文件。CDF文件主要放在以下目录:

        installDir/vxworks-6.x/target/config/comps/vxWorks

以下以一个PCI总线控制器的CDF文件为例,这个文件的路径是:

        installDir/vxworks-6.x/target/config/comps/vxWorks/40m85xxPci.cdf:

/* 40m85xxPci.cdf - Component configuration file */  
Component DRV_PCIBUS_M85XX {  
    NAME M85xx PCI bus  
    SYNOPSIS M85xx PCI bus controller Driver  
    MODULES m85xxPci.o  
    SOURCE $(WIND_BASE)/target/src/hwif/busCtlr  
    _CHILDREN FOLDER_DRIVERS  
    _INIT_ORDER hardWareInterFaceBusInit  
    INIT_RTN m85xxPciRegister();  
    PROTOTYPE void m85xxPciRegister (void);  
    REQUIRES DRV_RESOURCE_M85XXCCSR \  
    INCLUDE_PARAM_SYS \  
    INCLUDE_PCI_BUS \  
    INCLUDE_PLB_BUS \  
    INCLUDE_VXBUS  
    INIT_AFTER INCLUDE_PCI_BUS  
} 

        CDF文件使用上述语句来定义一个组件ID。VxWorks中的每个组件必须用Component关键字来描述,驱动程序的ID一般以DRV_开始,并在ID中包含该驱动程序的描述性信息,每一类的驱动程序对组件ID都有着相同的命名习惯,比如DRV_PCIBUS_M85XX意味着它是一个PCI 总线控制器的组件。设备驱动程序组件标准的命名习惯应该是:DRV_CLASS_NAME,组件名字必须是唯一的,因此DRV_CLASS_NAME中的DRV和CLASS都有可能相同,这就要求NAME必须唯一才行。如果为第三方驱动写CDF文件,可以考虑把VENDOR和驱动程序名作为NAME的一部分。总之,保证DRV_CLASS_NAME唯一就可以。

注意:以前老的驱动程序一般以INCLUDE_开头,不过新驱动都更改了这一用法,使用DRV_开头。

NAME M85xx PCI bus

        这是名字域,可读性较强,会在内核配置工具的名字栏显示该域定义的信息

SYNOPSIS M85xx PCI bus controller Driver

        摘要域,主要是对该组件的简短介绍

MODULES m85xxPci.o

        模块域,它列举了编译驱动程序生成的目标文件的名字。当一个驱动程序被包含在工程中时,VxWorks配置服务会分析模块域定义的目标文件中的内容,以决定是否会包含其他组件来把该驱动程序编译进VxWorks镜像。MODULES和REQUIES域一起,提供了把驱动程序编译进VxWorks内核所有需要的信息。

        _CHILDREN FOLDER_DRIVERS

这个域用来帮助Workbench对相似的组件的显示进行管理,如下图:

_INIT_ORDER hardWareInterFaceBusInit

        这个初始化顺序域描述的是:在VxWorks启动的过程中,应该在什么时候初始化本驱动程序,所有的总线驱动程序都必须在hardWareInterFaceBusInit中初始化,PCI总线也不例外。

INIT_RTN m85xxPciRegister();

        定义设备驱动程序的初始化方法,因此,我们必须在这个域提供驱动程序注册方法。这个只是一个初级或者说最小的初始化,后续的初始化会等到VxWorks找到相关的硬件后就会将驱动程序和硬件绑定,形成了一个实例。

PROTOTYPE void m85xxPciRegister (void);

        驱动注册方法的原型定义

REQUIRES …

        该域描述了驱动程序能够在VxWorks中正常工作所必须有的组件。这个域是必须存在的,因为不是所有设备驱动程序的依赖性都能够通过检查驱动中的unresolved externals来决定的。为了支持该驱动程序,它和MODULES域一起决定哪些组件还需要被包含进来。

INIT_AFTER INCLUDE_PCI_BUS

        这个INIT_AFTER和INIT_BEFORE被用于指明INIT_ORDER的依赖性,INIT_AFTER说明必须先初始化PCI总线,才能够初始化本驱动。INIT_BEFORE是在这之前必须要初始化本驱动。

HDR_FILES $(WIND_BASE)/target/src/hwif/h/end/fei8255xVxbEnd.h

        上面的这行代码并没有在例子中出现,HDR_FILES主要提供驱动注册函数的原型

CFG_PARAMS参数配置

        在初始化期间,有时候设备驱动程序需要一些配置信息,如果这些信息是和设备而不是实例相关的,则可以再编译阶段通过传递参数来实现。CFG_PARAMS和Parameter关键字将帮你实现这些任务。CFG_PARAMS用来指明组件要用的参数,而Parameter用来定义参数。如:

Component DRV_NET_SAMPLE {  
    NAME network device supporting jumbo frames  
    ...  
    CFG_PARAMS SAMPLE_JUMBO_MTU_VALUE  
}  

Parameter SAMPLE_JUMBO_MTU_VALUE {  
    NAME Jumbo frame MTU size  
    SYNOPSIS max num of bytes in a jumbo MTU  
    TYPE int  
    DEFAULT 9000  
} 

        每个参数都必须有NAME,SYNOPSIS,TYPE,DEFAULT四个域。TYPE描述了参数的数据类型,包括任何C类型,BOOL,string(NULL-terminated);DEFAULT定义的值是参数的默认值。

驱动程序配置文件

        对于某些BSP,VxWorks支持两种不同的方式来构建VxWorks镜像,一是使用Workbench或者vxprj命令行工具,二是直接在BSP目录中调用make命令。所有的BSP都支持第一种方法,尽管大多数都支持make方式,但是并不是所有的VxWorks开发环境和SMP都支持。

        当从makefile文件编译BSP时,CDF里面的信息并不用来配置BSP,程序员反而可以在BSP源文件config.h里面直接通过INCLUDE和EXCLUDE添加、删除某些组件,例如,如果你把Cn3xxx定时器驱动程序包含进你的VxWorks镜像中,可以在BSP中定义:#define DRV_TIMER_CN3XXX,为了简单起见,这里假设DRV_TIMER_CN3XXX不依赖于其他组件。修改完config.h文件后,你可以在BSP目录中直接调用make工具来编译BSP,编译BSP后,刚刚添加的定时器驱动程序已经可以被包含进VxWorks镜像了。

        为了支持驱动程序在BSP中直接编译,你必须创建两个附加文件,前面提过的DC和DR文件,这两个文件的作用就是把驱动程序和BSP命令行工具关联起来。dc文件的名字必须和驱动程序源文件的名字一样(后缀不一样嘛),以CN3XXX定时器驱动程序为例,vxbCn3xxxTimer.dc文件如下:​​​​​​​  

IMPORT void vxbCn3xxxTimerDrvRegister();

         DC文件主要是提供驱动程序注册方法的原型声明,或者以#ifdef../#endif来包含住:

#ifdef DRV_TIMER_CN3XXX
    IMPORT   void vxbCn3xxxTimerDrvRegister();
#endif

        DR文件就更加简洁了,描述了如何调用驱动程序注册方法,vxbCn3xxxTimer.dr文件如下:

#ifdef DRV_TIMER_CN3XXX
    vxbCn3xxxTimerDrvRegister();
#endif

        对于第三方驱动,DR和DC文件在驱动程序的目录下。为了使这些文件起作用,它们必须被合并到VxWorks镜像的初始化文件中,当新的驱动被增加到VxWorks源码树时,可以使用以下命令来重新创建初始化文件:

cd installDir/vxworks-6.x/target/config/comps/src/hwif
make vxbUsrCmdLine.c

        当执行MAKE命令时,它会搜索所有的驱动程序配置文件,并一起合并到vxbUsrCmdLine.c文件中,该文件的目录在:

        installDir/vxworks-6.x/target/config/all/vxbUsrCmdLine.c

注意:vxbUsrCmdLine.c文件在以下几种情况下是不会被更新的:vxbUsrCmdLine.c的修改时间比dc、dr文件和installDir/vxworks-6.x/target/config/comps/src/hwif目录中的文件的修改时间要更新,在这种情况下,把以前的vxbUsrCmdLine.c文件重命名,并重新执行MAKE命令即可。

README 文件

        虽然MAKE环境并不要求有README文件,但是每个设备驱动程序都应该提供README文件给最终用户。第三方厂商可能会在README文件里面描述驱动版本信息、驱动程序的文件列表、已知的缺陷等信息。README文件包括三个数据段和三个行分隔符:

  • 使用一行文字来表明这是某某驱动程序的README文件,可能会包括支持的设备信息,如: README: VxWorks/VxBus driver for device device
  • 第一个数据段和第二个数据段的分隔符
  • 该数据段描述本驱动程序支持的设备列表、已经测试过的设备,也描述了驱动程序的VxWorks和VxBus版本信息,最后还包括组成本驱动的所有文件列表等。
  • 第二个数据段和第三个数据段的分隔符
  • 驱动程序版本信息列表,以及各个版本的变更信息

注意:驱动程序版本信息包括两个部分(例如2.3),不能使用3部分的版本信息格式,README文件的一个例子目录在:installDir/vxworks-6.x/target/3rdparty/windriver/wrsample

设备驱动程序Makefiles

        为了在VxWorks环境中正确编译设备驱动程序,你必须提供合适的Makefiles。有两种makefile来帮助你解决问题,第一种是厂商的makefile,目录位于:installDir/vxworks-6.x/target/3rdparty/vendor/Makefile,第二种是驱动程序的makefile,目录位于:installDir/vxworks-6.x/target/3rdparty/vendor/driver/Makefile。这些makefiles里面的内容有点复杂,因此可以在以下目录拷贝一个makefile模板,然后修改即可:

installDir/vxworks-6.x/target/3rdparty/windriver
installDir/vxworks-6.x/target/3rdparty/windriver/wrsample
(1) 厂商makeflie

        厂商的makefile是被同一个厂商目录下的所有驱动程序共享的,它使用通配符来确定哪些驱动程序被安装到了厂商目录下,并为每一个设备驱动程序调用make命令。从目录installDir/vxworks-6.x/target/3rdparty/windriver拷贝makefile模板,并添加到对应厂商目录下installDir/vxworks-6.x/target/3rdparty/vendor,你可以根据makefile模板做相应的修改。

(2) 驱动程序makefile

        在以下目录创建驱动程序makefile

installDir/vxworks-6.x/target/3rdparty/vendor/driver

        这个makefile文件用于编译驱动程序子目录中的源文件,这个makefile也可以从以下目录拷贝并加以修改:installDir/vxworks-6.x/target/3rdparty/windriver/wrsample。但是,跟厂商makefile不一样的是,驱动程序makefile不使用通配符来寻找驱动程序源文件,相反,它包含一个要生产的目标文件列表,并说明如何从源文件中编译生成这些目标文件。wrsample中的makefile的comment能够帮助你根据驱动程序来修改makefile,不过通常情况下,主要是修改LIB_BASE_NAME(公司名字),和OBJ_COMMON(要生产的目标文件列表)。另外如果你只想为某一CPU平台编译驱动程序,可以使用该CPU平台的定义来代替OBJ_COMMON,如POWERPC平台(OBJ_PPC32)。

参考:VxWorks vxbus驱动程序的组织结构

--------------------------------------------------------------------------------------------------

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值