(转贴)Windows CE 5.0下串口驱动硬件FIFO控制Bug分析及修正方法

转贴自:驱动开发网

原贴地址:http://bbs.driverdevelop.com/read.php?tid=109193&fpage=0&toread=&page=1

作者:周群威

摘要:详细分析了Windows CE 5.0下串口驱动程序中硬件FIFO控制的一个Bug,并给出了修改方法,对修改前后的驱动程序的性能进行了测试,测试结果表明改进后的驱动性能得到了很大的提升。
   
关键词:Windows CE    WinCE   串口驱动   串口丢包 

仔细阅读Windows CE 5.0下串口驱动关于硬件FIFO控制后,发现一个Bug,该Bug的表现是通过注册表设置的硬件FIFO大小无效,实际的驱动只会将硬件FIFO触发点设置成最小值(无FIFO)和最大值(7/8满FIFO)时触发中断,这一Bug导致了在Windows CE5.0下用高波特率(如115200)的总线外扩标准16C2550串口芯片连续接收大量数据时容易出现丢包现象。(实际测试时使用PXA270外部总线扩展SC16C2550B串口芯片,在115200的波特率下很容易出现丢包现象(无硬件流控),如果在通信过程中有其它驱动产生中断(如插入USB Device,则这种现象更为严重))。
1. Windows CE 5.0下串口驱动硬件FIFO控制Bug分析
首先我我们看CPdd16550::GetWaterMarkBit()函数,该函数的作用是查表得出硬件FIFO触发点设置的掩码位。该函数的源代码在WINCE500/public/common/oak/drivers/serial/ oo16550/pdd16550.cpp中(约第467行)。CPdd16550::GetWaterMarkBit()函数的实现代码如程序清单 1.1所示。
程序清单 1.1 CPdd16550::GetWaterMarkBit()函数
/*
* 硬件FIFO触发点设置掩码位表
*/
static PAIRS s_HighWaterPairs[] = {
    {SERIAL_1_BYTE_HIGH_WATER, 0},
    {SERIAL_4_BYTE_HIGH_WATER, 4},
    {SERIAL_8_BYTE_HIGH_WATER, 8},
    {SERIAL_14_BYTE_HIGH_WATER, 14}
};

BYTE  CPdd16550::GetWaterMarkBit()
{
    BYTE bReturnKey = (BYTE)s_HighWaterPairs[0].Key;
    for (DWORD dwIndex=dim(s_HighWaterPairs)-1;dwIndex!=0; dwIndex --) {    /* 从表尾开始查找    */
        if (m_dwWaterMark>=s_HighWaterPairs[dwIndex].AssociatedValue) {      /* 找到            */
            bReturnKey = (BYTE)s_HighWaterPairs[dwIndex].Key;            /* 记录掩码位的值 */
            break;
        }
    }
    return bReturnKey;                                            /* 返回掩码位       */
}

该程序中,PAIRS结构体在WINCE500/PUBLIC/COMMON/OAK/INC/pdd16550.h中定义,它的原型如程序清单 1.2所示。
程序清单 1.2 PAIRS结构体
typedef struct  __PAIRS {
    ULONG   Key;
    ULONG   AssociatedValue;
} PAIRS, *PPAIRS;

SERIAL_1_BYTE_HIGH_WATER、SERIAL_4_BYTE_HIGH_WATER等宏定义在WINCE500 /PUBLIC/COMMON/OAK/INC/hw16550.h中,如程序清单 1.3所示。
程序清单 1.3 相关宏定义
#define SERIAL_1_BYTE_HIGH_WATER   ((UCHAR)0x00)
#define SERIAL_4_BYTE_HIGH_WATER   ((UCHAR)0x40)
#define SERIAL_8_BYTE_HIGH_WATER   ((UCHAR)0x80)
#define SERIAL_14_BYTE_HIGH_WATER  ((UCHAR)0xc0)

我们来分析一下CPdd16550::GetWaterMarkBit()函数,它的返回值只能是0x00、0x40、0x80和0xC0四个中的一个。而要准确地计算出它的返回值,我们需要得到CPdd16550的一个成员变量m_dwWaterMark的值,m_dwWaterMark的值在CPdd16550::Init()函数中初始化,我们再来看看CPdd16550::Init()函数的实现代码,如程序清单 1.4所示。
程序清单 1.4 CPdd16550::Init()函数
BOOL CPdd16550::Init()
{
    if ( CSerialPDD::Init() && IsKeyOpened() && m_XmitFlushDone!=NULL) {
        // IST Setup .
        DDKISRINFO ddi;
        if (GetIsrInfo(&ddi)!=ERROR_SUCCESS) {
            return FALSE;
        }
        m_dwSysIntr = ddi.dwSysintr;
        if (m_dwSysIntr !=  MAXDWORD && m_dwSysIntr!=0 )
            m_hISTEvent= CreateEvent(0,FALSE,FALSE,NULL);
       
        if (m_hISTEvent!=NULL) {
            if (!InterruptInitialize(m_dwSysIntr,m_hISTEvent,0,0)) {
                m_dwSysIntr = MAXDWORD ;
                return FALSE;
            }
        }
        else
            return FALSE;
        // Get Device Index.
        if (!GetRegValue(PC_REG_DEVINDEX_VAL_NAME,
(PBYTE)&m_dwDevIndex, PC_REG_DEVINDEX_VAL_LEN)) {
            m_dwDevIndex = 0;
        }
/*
* 从注册表读取硬件FIFO大小
*/
        if (!GetRegValue(PC_REG_SERIALWATERMARK_VAL_NAME,                     (1)
(PBYTE)&m_dwWaterMark,sizeof(DWORD))) {
            m_dwWaterMark = 8;
        }
        if (!MapHardware() || !CreateHardwareAccess()) {
            return FALSE;
        }
        return TRUE;
    }
    return FALSE;
}

程序清单 1.4(1)从注册表读取硬件FIFO的大小到CPdd16550的成员变量m_dwWaterMark中,如果注册表中没有这个设置值,则使用默认值8。即8个字节的硬件FIFO。
最后我们来看看串口驱动是怎么设置串口FIFO硬件流控的。如程序清单 1.5所示。
程序清单 1.5 CPdd16550::InitReceive()函数
BOOL    CPdd16550::InitReceive(BOOL bInit)
{
    m_HardwareLock.Lock();   
    if (bInit) {        
        BYTE uWarterMarkBit = GetWaterMarkBit();       ( 1 )/* 读取硬件FIFO触发点设置掩码位*/
        if (uWarterMarkBit> 3)                          ( 2 )/* 检查合法性                */
            uWarterMarkBit = 3;
        m_pReg16550->Write_FCR(m_pReg16550->Read_FCR() |
SERIAL_FCR_RCVR_RESET |
SERIAL_FCR_ENABLE |
(uWarterMarkBit<<6));  ( 3 ) /* 设置硬件FIFO触发点          */
        m_pReg16550->Write_IER(m_pReg16550->Read_IER() | SERIAL_IER_RDA);
        m_pReg16550->Read_LSR(); // Clean Line Interrupt.
    }
    else {
        m_pReg16550->Write_IER(m_pReg16550->Read_IER() & ~SERIAL_IER_RDA);
    }
    m_HardwareLock.Unlock();
    return TRUE;
}

程序清单 1.5(1)获取串口硬件FIFO触发点掩码位,然后检查它的合法性(程序清单 1.5(2))如果大于最大值3,则取最大值3(因为硬件FIFO的触发点只有四个等级,最大值为3)。程序清单 1.5(2)将硬件FIFO触发点掩码位写入到串口硬件的FCR(FIFO控制寄存器)中。
注:对于标准16C2550的串口,FCR的第7~6(两位)对应硬件FIFO触发点设置。共四个等级,00B1字节触发;01B4字节触发;10B8字节触发;11B14字节触发。对于标准16C2550串口FIFO的大小是16字节。
由前面对CBulPdd16550::GetWaterMarkBit()函数的分析可知GetWaterMarkBit()函数的返回值是0x00、0x40、0x80和0xC0四个中的一个,在默认情况下的返回值是0x80。由于GetWaterMarkBit()函数的返回值的后三个值都大于3,这样,就导致了只要串口驱动注册表中硬件FIFO的设置值大于3,驱动就会将硬件FIFO的触发点设置在7/8的位置上(14字节),这样,当连续接收大量数据时,如果串口IST不能及时读走FIFO中的数据(发生中断时FIFO再接收两个字节就满),将导致串口通信出现丢失数据的情况,当系统任务繁重时这种情况就会更加严重。
2. 修正方法
由前面的分析可知,这个Bug主要是由CBulPdd16550::GetWaterMarkBit()函数引起的,可以有以下几个方法来修正这个Bug:
方法一:修改程序清单 1.3的宏定义如程序清单 2.1所示,加粗的代码为修改了的地方。
程序清单 2.1 修改宏定义
#define SERIAL_1_BYTE_HIGH_WATER   ((UCHAR)0x00)
#define SERIAL_4_BYTE_HIGH_WATER   ((UCHAR)0x01)
#define SERIAL_8_BYTE_HIGH_WATER   ((UCHAR)0x02)
#define SERIAL_14_BYTE_HIGH_WATER  ((UCHAR)0x03)

这样,在程序清单 1.5 CPdd16550::InitReceive()函数中,当调用GetWaterMarkBit()函数时,就能得到正确的掩码位。
方法二:修改程序清单 1.5(3)的代码如程序清单 2.2加粗的代码所示。这跟方法一的原理是一样的。
程序清单 2.2修改CPdd16550::InitReceive()函数
m_pReg16550->Write_FCR(m_pReg16550->Read_FCR() |
SERIAL_FCR_RCVR_RESET |
SERIAL_FCR_ENABLE |
                                (uWarterMarkBit));         ( 3 ) /* 设置硬件FIFO触发点      */

方法三:修改CBulPdd16550::GetWaterMarkBit()函数,如程序清单 2.3加粗的代码所示。
程序清单 2.3 修改CPdd16550::GetWaterMarkBit()函数
BYTE  CPdd16550::GetWaterMarkBit()
{
    BYTE bReturnKey = (BYTE)s_HighWaterPairs[0].Key;
    for (DWORD dwIndex=dim(s_HighWaterPairs)-1;dwIndex!=0; dwIndex --) {    /* 从表尾开始查找    */
        if (m_dwWaterMark>=s_HighWaterPairs[dwIndex].AssociatedValue) {      /* 找到            */
            bReturnKey = (BYTE)s_HighWaterPairs[dwIndex].Key;            /* 记录掩码位的值 */
            break;
        }
    }
    return ((bReturnKey >> 6) & 0x03);                                /* 返回掩码位       */
}
由于CBulPdd16550::GetWaterMarkBit()函数被声明为虚函数,因此,我们可以继承CBulPdd16550类来生成一个新的类CExtPdd16550FUART,在该类中重新实现GetWaterMarkBit()函数并修正这个Bug,这样,就可以在不改动微软驱动源代码的情况下轻松修正这个Bug。其实现源代码如程序清单 2.4所示。(推荐大家用这个方法)此代码放到/WINCE500/PLATFORM/MAINSTONEII/src/drivers/serial/ms2_serial.cpp中。(对于MAINSTONEII平台)
程序清单 2.4 重新实现GetWaterMarkBit()函数
/*
*  硬件FIFO掩码位表
*/
static PAIRS s_HighWaterPairs[] = {
        {SERIAL_1_BYTE_HIGH_WATER, 0},
        {SERIAL_4_BYTE_HIGH_WATER, 4},
        {SERIAL_8_BYTE_HIGH_WATER, 8},
        {SERIAL_14_BYTE_HIGH_WATER, 14}
    };
/****************************************************************************************
** Function name:   CExtPdd16550FUART
** Descriptions:    外扩串口对象
**------------------------------------------------------------------------------------------------------------
** Modified by:     周群威
** Modified Date:   2008-02-16
**-----------------------------------------------------------------------------------------------------------
******************************************************************************************/
class CExtPdd16550FUART : public CPdd16550 {
public:

    CExtPdd16550FUART (LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj)
        : CPdd16550 (lpActivePath,pMdd, pHwObj) {
    };
    ~CExtPdd16550FUART()
    {
    }
/******************************************************************************************
** Function name:   CExtPdd16550FUART::GetWaterMarkBit
** Descriptions:    获取串口硬件FIFO触发点掩码
** Input:          无
** Output:         无
** Return:         串口硬件FIFO触发点掩码
** Note:           PDD16550实现方法有Bug,所以在这里重新实现该方法,详见该函数的注释
** Created by:      MicroSoft
** Created Date:    未知
**----------------------------------------------------------------------------------------------------------------
** Modified by:     周群威
** Modified Date:   2008-02-16
**-------------------------------------------------------------------------------------------------------------
******************************************************************************************/
    virtual BYTE GetWaterMarkBit()
    {
        BYTE bReturnKey = (BYTE)s_HighWaterPairs[0].Key;
        for (DWORD dwIndex=dim(s_HighWaterPairs)-1;dwIndex!=0; dwIndex --) {     /*  查表     */
            if (m_dwWaterMark>=s_HighWaterPairs[dwIndex].AssociatedValue) {     /*  找到      */
                bReturnKey = (BYTE)s_HighWaterPairs[dwIndex].Key;
                break;
            }
        }
        /*
         *  原来的代码(在Public目录下串口驱动的PDD16550类中的实现方法是直接返回bReturnKey
         *  导至在后面设置硬件FIFO时发现FIFO触发点掩码位>3,所以将硬件FIFO触发点设到了最大
         *  值上而不管注册表的的设置值是多少,从而导至了当串口接收大量数据时由于中断服务线程
         *  的延时容易引起串口接收丢包(因为串口中断时,FIFO已经要满了,如果中断服务线程没有及时
         *  读走数据则新发过来的数据由于没有FIFO而丢失)
         */
        return ((bReturnKey >> 6) & 0x03);
    }
};

/****************************************************************************************
** Function name:   CreateSerialObject
** Descriptions:     创建串口驱动PDD层对象
** Input:           lpActivePath,串口驱动注册表路径
**                pMdd,串口驱动MDD层对象指针
**                pHwObj,串口驱动硬件操作函数结构
**                DeviceArrayIndex,串口类型
** Output:          无
** Return:          串口对象指针
** Note:           
** Created by:      MicroSoft
** Created Date:    未知
**------------------------------------------------------------------------------------------------------------------
** Modified by:     周群威
** Modified Date:   2008-02-16
**-----------------------------------------------------------------------------------------------------------------
******************************************************************************************/
CSerialPDD * CreateSerialObject(LPTSTR lpActivePath,
PVOID pMdd,
PHWOBJ pHwObj,
DWORD DeviceArrayIndex)
{
    CSerialPDD * pSerialPDD = NULL;
    switch (DeviceArrayIndex ) {                      /*  根据串口类型生成不同的串口对象实例*/
        case 0:default:
            pSerialPDD = new CPdd16550(lpActivePath,pMdd, pHwObj);
            break;
        case 0x80:
            pSerialPDD = new CBulPdd16550FUART (lpActivePath,pMdd, pHwObj);
            break;
        case 0x81:
            pSerialPDD = new CBulPdd16550BUART(lpActivePath,pMdd, pHwObj);
            break;
        case 0x82:
            pSerialPDD = new CBulPdd16550SUART(lpActivePath,pMdd, pHwObj);
            break;
        case 0x84:                                /*  外扩串口在这时定义为0x84     */
            DEBUGMSG(1,(_T("Load Ext Uart /r/n")));
            pSerialPDD = new CExtPdd16550FUART(lpActivePath,pMdd, pHwObj);
            break;
    }
    if (pSerialPDD && pSerialPDD->Init()!= TRUE) {       /*  初始化串口驱动PDD层对象     */
        delete pSerialPDD;
        pSerialPDD = NULL;
    }
    return pSerialPDD;
}
经以上修改后再重新测试串口的性能,发现在没有USB Device插入中断时,串口连续收发大量数据时的正确率为100%(专门写了一个两个外扩串口数据对发测试程序,一次收发100字节再比较)。而没有修改前的正确率为96%左右。如果在测试过程中有USB Device插入的话(有中断产生),则在插入时可能丢失几个字节,这可以通过换用更大FIFO的串口芯片来解决,也可以通过注册表来提高串口驱动IST的优先级来解决,实践表明,只要串口IST的优先级小于100(因为在Windows CE 5.0驱动的默认优先级中,USB Device的最高,为100),串口就不会丢包了。
3. 参考资料
Platform Builder帮助文档
Windows CE 5.0串口驱动源代码

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值