C64x系列DSP/BIOS中设备驱动程序的设计
随着新技术的不断涌现和DSP实时系统的日趋复杂,不同类型的外部设备越来越多。为这些外部设备编写驱动程序已经成为依赖操作系统管理硬件的内在要求。但是,由于内存管脚、响应时间和电源管理等条件的限制,为一个给定的DSP系统编写设备驱动程序有时候会很困难。针对设备驱动程序开发者遇到的上述难题,TI公司为C64x系列[1]DSP的开发者提供了一种类/微型驱动模型(class/mini-driver model)[2]。该模型在功能上将设备驱动程序分为依赖硬件层和不依赖硬件层两层,两层之间使用通用接口。实践结果表明,采用类/微型驱动模型进行设计后,应用软件可以复用绝大部分相似设备的驱动程序,从而提高驱动程序的开发效率。
1 类/微型驱动模型简介
在类/微型驱动模型中,类驱动通常用于完成多线程I/O请求的序列化功能和同步功能,同时对设备实例进行管理。在包括视频系统I/O和异步I/O的典型实时系统中,只有少数的类驱动需要表示出外部设备的类型。
类驱动通过每个外部设备独有的微型驱动对设备进行操作。微型驱动通过控制外设的寄存器、内存和中断资源对外部设备实现控制。微型驱动程序必须将特定的外部设备有效地表示给类驱动。例如:视频显示设备存在一些不同的帧存,应用软件会根据不同的I/O操作进行帧存的分配,此时微型驱动必须映射视频显存,使得类驱动可以对不连续的内存(分别存放RGB或YUV分量)设计特定的I/O请求。
类/微型驱动模型允许发送由开发者定义数据结构的I/O请求包给微型驱动来控制外部设备,此分层结构使设备驱动的复用能力得到加强,并且丰富了发送给微型驱动的I/O请求包的结构。
类/微型驱动模型结构如图1所示。上层的应用程序不直接控制微型驱动,而是使用一个或一个以上的类驱动对其进行控制。每一个类驱动在应用程序代码中表现为一个API[3]函数并且通过微型驱动的接口IOM与微型驱动进行通信。类驱动使用DSP/BIOS中的API函数实现诸如同步等的系统服务。
类驱动通过标准的微型驱动接口调用微型驱动控制硬件设备。到目前为止DSP/BIOS共定义了三种类驱动:流输入输出管理模块(SIO)、管道管理模块(PIP)和通用输入输出模块(GIO)。在PIP和SIO类驱动中,调用的API函数已经存在于DSP/BIOS的PIP和SIO模块中。这些API函数需将参数传给相应的适配模块(adapter),才能与微型驱动交换数据。而在GIO类驱动中,调用的API函数则直接与微型驱动通信(需在CCS2.2以上)。
每一个微型驱动都为类驱动和DSP/BIOS设备驱动管理提供了标准接口。微型驱动采用芯片支持库(Chip Support Library)[4]管理外围设备的寄存器、内存和中断资源。
2 类驱动的编写
SIO和PIP两个接口模块用于支持DSP和外设之间的数据交换。这两种模块都可以通过类驱动中的适配模块和微型驱动的IOM连接进行数据传输。SIO的适配模块称为DIO,PIP的适配模块称为PIO。
GIO模块[5]的传输模式是基于流输入输出模式的同步I/O模式,更适合文件系统I/O。在编写类驱动时,可以直接调用GIO的读写API函数,这些函数的接口已经内置于微型驱动的IOM中。
2.1 SIO模块和DIO模块
DSP/BIOS中的SIO模块为每个DSP/BIOS线程提供一个独立的I/O机制,它支持动态创建。SIO模块有自己的驱动模型,称为DEV。DEV程序和微型驱动的编写方法相似,都要实现函数表中的打开、关闭和缓存管理等函数,然而结构比较复杂。相比之下,DIO模块可以简化SIO模块和IOM之间的连接,使得通信和同步变得更简单。
DIO模块必须实现下列基本功能函数:
(1)回调函数 在外设的通道实例创建结束时,如果微型驱动已经完成内存分配,那么适配模块将通过回调函数通知微型驱动待调用函数的地址,同时回调函数也将通知适配模块缓存已经建立,并最终通知上层应用程序。
(2)传输函数 传输函数将调用微型驱动中的mdSubmitChan函数。微型驱动中的mdSubmitChan函数将从适配模块获得一块缓存,并将缓存中的新信息通过通道实例通知给中断服务程序(ISR)。DIO模块通过传输函数实现应用程序与微型驱动之间的通信。
2.2 PIP模块和PIO模块
DSP/BIOS中PIP模块提供管理异步I/O的数据管道。每个管道对象都拥有一块同样大小的缓存,这些缓存分别为同样数量的等长小块。小块的数量和长度在DSP/BIOS中设置。虽然小块的长度是固定的,但应用程序可以把小于这个长度的数据放入缓存小块中。一个管道有两个结束状态:写完缓存和读完缓存。通常,无论哪个结束状态都会激活I/O设备。数据通知函数用来执行读写同步任务和通知PIP缓存填满或清空。写数据时,PIP_alloc函数用来获得缓存,PIP_put函数用于将数据写入缓存。写完后,读数据通知函数notifyReader将被调用。读数据时,PIP_get函数用来接收缓存中的数据,PIP_free函数在数据不再被使用时将缓存清空。清空完后,写数据通知函数notifyWriter将被调用。
PIO模块通过PIP模块从应用程序中获得缓存,并将获得的缓存提供给微型驱动使用。当微型驱动使用完缓存时,PIO模块还可以将缓存交还给应用程序。
PIO模块必须实现下列基本功能函数:
(1)主函数 当应用程序给设备分配缓存时,PIP的缓存管理调用rxPrime和txPrime函数。这两个函数调用DSP/BIOS的API函数获得缓存并提供给微型驱动使用。主函数负责给适配模块和应用程序的缓存分配发送起始信号。
(2)回调函数 当微型驱动已完成内存分配时,适配模块通过回调函数rxCallback或txCallback通知微型驱动待调用函数的地址,同时回调函数也通知适配模块缓存已经建立,并最终通知给上层应用程序。
(3)传输函数:传输函数将调用微型驱动中的mdSubmitChan函数。mdSubmitChan函数将从适配模块中获得一块缓存,并将缓存的新信息通过通道实例通知给中断服务程序(ISR)。PIO模块通过传输函数实现应用程序与微型驱动之间的通信。
2.3 GIO模块
GIO模块在提供必要的同步读/写API函数及其扩展函数的同时,将代码和使用数据缓存的大小尽量简化。如图2所示,应用程序可以调用GIO的API函数直接与微型驱动的IOM交换数据,这些API函数使得GIO成为了第三种类驱动。
当调用GIO_create创建一个外部设备的通道实例时,GIO在通道实例中增加了状态和I/O请求状态结构、IOM数据包(IOM_Packets)及一个GIO数据对象。GIO创建的通道实例的数据结构如下:
typedef struct GIO_Obj {
IOM_Fxns *fxns; /* 函数表指针 */
Uns mode; /* 创建模式 */
Uns timeout; /* 超时时间 */
IOM_Packet syncPacket; /* 同步时使用的IOM_Packet */
QUE_Obj freeList; /* 异步I/O队列 */
Ptr syncObj; /* 同步对象地址 */
Ptr mdChan; /* 通道实例地址 */
} GIO_Obj, *GIO_Handle;
函数表指针是应用程序和微型驱动函数表(fxns)的接口;创建模式包括:输入(IOM_INPUT)、输出(IOM_OUTPUT)和双向(IOM_INOUT);IOM_Packet在类驱动和微型驱动间的异步操作时使用;同步对象地址指向特定通道的同步信号;通道实例地址指向微型驱动创建的通道实例。
3 微型驱动的设计和实现
类/微型驱动模型中的微型驱动直接控制外部设备。只要微型驱动创建了规定的函数,应用程序就可以方便地通过DIO适配模块、PIO适配模块或(和)GIO类驱动调用。这些规定的函数包括:通道绑定函数(mdBindDev)、通道创建/删除函数(mdCreateChan/mdDeleteChan)、I/O请求发送函数(mdSubmitChan)、中断服务函数(ISRs)和设备控制函数(mdControlChan)。这些规定的函数将放入微型驱动的函数接口表(IOM_Fxns)中的相应位置,供应用程序通过适配模块或GIO类驱动调用。函数接口表的结构如下:
typedef struct IOM_Fxns
{
IOM_TmdBindDev mdBindDev;
IOM_TmdUnBindDev mdUnBindDev;
IOM_TmdControlChan mdControlChan;
IOM_TmdCreateChan mdCreateChan;
IOM_TmdDeleteChan mdDeleteChan;
IOM_TmdSubmitChan mdSubmitChan;
} IOM_Fxns;
3.1 绑定通道函数
DSP/BIOS设备初始化时将调用每个已注册到微型驱动中的绑定函数(mdBindDev)。绑定函数一般要实现下列功能:根据配置的设备参数和可能存在的全局设备数据初始化外围设备;挂入中断服务函数(ISRs);获得缓存、McBSPs、McASPs和DMA等资源。
如果微型驱动使用多个外部设备,则DSP/BIOS为每个外设调用绑定函数。设备参数devid用来区分设备。如果支持一个设备,则绑定函数必须检查是否已经有设备绑定。
微型驱动如果使用静态数据来减少实时处理的动态数据分配,可以使用输入/输出数据指针(devp)。输入/输出数据指针将传给通道创建函数(mdCreateChan)。
3.2 通道创建/删除函数
从应用的观点出发,在应用程序和外部设备之间必须有一个逻辑交流通道用来交换数据。应用程序通过微型驱动创建一个或多个逻辑通道对象作为应用程序的逻辑通道。通道创建函数(mdCreateChan)根据需要创建通道对象并给通道对象设置初始值。通道删除函数(mdDeleteChan)则删除已创建好的通道对象。虽然每个微型驱动的通道对象数据结构都略有不同,但有些字段是必须的,如通道模式、等待I/O 包序列和回调函数。以下是一个常见的通道对象数据结构:
typedef struct ChanObj {
Bool inuse; /*如果为TRUE,则通道已打开*/
Int mode; /*通道模式*/
IOM_Packet *dataPacket; /*I/O 包*/
QUE_Obj pendList; /*等待I/O 包序列*/
Uns *bufptr; /*当前缓存指针*/
Uns bufcnt; /*未处理的缓存数目*/
IOM_TiomCallback cbFxn; /*回调函数*/
Ptr cbArg; /*回调函数参数地址*/
} ChanObj, *ChanHandle;
3.3 I/O请求发送函数
微型驱动中的I/O请求发送函数(mdSubmitChan)用来处理IOM_Packet包中的命令字段。根据不同命令字段,微型驱动将处理命令或返回错误信息(IOM_ENOTIMPL)。
微型驱动支持的命令字段有:IOM_READ、IOM_WRITE、IOM_ABORT和IOM_FLUSH。微型驱动创建的输入通道由IOM_READ命令来执行输入任务,创建的输出通道则由IOM_WRITE命令来执行输出任务。要放弃或者刷新已经发送的I/O请求,可以使用IOM_ABORT或IOM_FLUSH命令。当放弃时,I/O请求包队列中的所有输入输出请求都将被放弃。当刷新时,所有的I/O输出包顺序执行,而所有的输入I/O包都被放弃。
3.4 中断服务函数
微型驱动的中断功能就是去处理外部设备的触发事件,例如周期性的中断。中断通常是表示外设采样完数据或者处理完数据,也可以用于为DMA提供同步信号,微型驱动必须处理这些中断。通常微型驱动中的中断服务函数ISRs必须完成以下功能:出列IOM_Packet请求;设置下一次传送或服务请求;调用类驱动的回调函数以保证和应用程序同步,并返回IOM_Packet。
3.5 设备控制函数
微型驱动支持的控制操作因不同的外部设备而异。IOM定义了一些通用的控制代码供驱动程序调用。特定设备独有的控制代码必须自己编写,其特征值必须大于128(IOM_CNTL_USER)。目前IOM支持的通用的控制代码有:
IOM_CHAN_RESET:将创建的通道实例重新恢复到初始状态。
IOM_CHAN_TIMEDOUT:当应用程序或类驱动超时时,此控制代码将进行超时操作。例如,一个超时的IOM_Packet,如果没执行回调函数,可能会被返回类驱动。
IOM_DEVICE_RESE:外部设备重新恢复到初始状态,它将影响为这个外部设备创建的所有通道实例。
微型驱动支持的控制代码和控制操作必须告诉使用微型驱动的应用程序开发者,特别要注明该代码的针对对象(是针对通道实例还是针对设备实例)。例如:改变外设波特率的控制代码,必须注明是针对某个通道或者所有通道的,否则容易给应用程序带来错误。
4 类/微型驱动模型驱动应用实例——C64x系列DSP/BIOS中PCI设备的驱动
4.l 微型驱动的设计与编写
(1)设计mdBindDev的部分程序代码:
static Int mdBindDev(Ptr *devp, Int devid, Ptr devParams)
{
……
QUE_new(&device.highPrioQue); /* 建立IOM包队列 */
QUE_new(&device.lowPrioQue);
……
hwiAttrs.ccMask = IRQ_CCMASK_NONE;
/*初始化PCI中断 */
hwiAttrs.arg = NULL;
IRQ_map(IRQ_EVT_DSPINT, intrId);
HWI_dispatchPlug(intrId, (Fxn)isr, -1, &hwiAttrs);
}
(2)设计mdCreateChan的部分程序代码
static Int mdCreateChan(Ptr *chanp, Ptr devp,String name,Int mode,Ptr chanParams, IOM_Tiom Callback cbFxn, Ptr cbArg)
{
……
chan = MEM_alloc(0, sizeof(ChanObj), 0);
chan->queue = &device.highPrioQue; /*通道初始化 */
……
if (device.openCount == 0) {
PCI_intEnable(PCI_EVT_PCIMASTER); /*PCI设备中断初始化 */
……
IRQ_enable(IRQ_EVT_DSPINT);
}
*chanp = chan; /*返回创建通道 */
}
(3)设计mdSubmitChan的部分程序代码
static Int mdSubmitChan(Ptr chanp, IOM_Packet *pPacket)
{
ChanHandle chan = (ChanHandle)chanp;/*挂载已创建通道 */
……
req=(C64XX_PCI_Request *)packet->addr;/*I/O请求包地址*/
req->reserved = chan;
……
/*处理读写请求包 */
if (packet->cmd == IOM_READ || packet->cmd ==IOM_WRITE) {
imask = HWI_disable();
QUE_enqueue(chan->queue, packet)
……
}
……/* 处理其它功能的请求包 */
removePackets(chan, packet->cmd);/*移除已处理的请求包 */
}
中断服务函数(ISRs)和设备控制函数(mdControlChan)的结构与以上I/O请求发送函数(mdSubmitChan)的结构类似,本文不再作叙述。
4.2 在DSP/BIOS中注册微型驱动
打开DSP/BIOS配置工具,如图3所示。右键点击User-Defined Devices图标,选择插入选项,并重新命名为PCICHAN。右键点击PCICHAN,选择属性选项,进行注册,如图4所示。
4.3 编写类驱动
本例的类驱动使用通用输入输出模块,首先右键点击图3中的GIO Manager,选择启动GIO。在应用程序中,GIO_create函数使用微型驱动PCICHAN来创建通道实例,通过调用GIO_submit函数完成应用程序对PCI设备的读写操作等。源代码如下:
(1)创建通道
GIO_Handle pciChan;
C64XX_PCI_Attrs pciChanParam;
C64XX_PCI_Request pciChanRequest;
C64XX_PCI_DevParams pciChanDevParam;
GIO_AppCallback pciChanCallBack;
pciChan= GIO_create('/PCICHAN',IOM_INOUT, &status, NULL, NULL);
(2)发送读请求包
pciChanRequest.srcAddr = (Ptr)BitsBuffer;
pciChanRequest.dstAddr = (Ptr)m_DspControl.CstartAddr;
pciChanRequest.byteCnt = length+20;
pciChanRequest.options = PCI_WRITE;
pciChanReqSize = sizeof(pciChanRequest);
status = GIO_submit(pciChan,IOM_WRITE,&pciChanRequest,&pciChanReqSize,NULL);
通过上述三个步骤,PCI设备的DSP/BIOS驱动设计就基本上完成了。应用程序可以通过使用类驱动来复用PCI设备,这样极大地提高了驱动的工作效率,对PCI外设的控制也大为简化了。
参考文献
1 TMS320C64x Technical Overview. Literature Number: SPRU395B.Texas Instruments Incorporated, January 2001
2 DSP/BIOS Driver Developer’s Guide. Literature Number: SPRU616. Texas Instruments Incorporated,November 2002
3 TMS320C6000 DSP/BIOS Application Programming Interface(API) Reference Guide. Literature Number: SPRU403E.Texas Instruments Incorporated,October 2002
4 TMS320C6000 Chip Support Library API Reference Guide.Literature Number: SPRU401E.Texas Instruments Incorporated,December 2002
5 TMS320C6000 Peripherals Reference Guide.Literature Number:SPRU190D. Texas Instruments Incorporated,February 2001