您的位置:首页 > 其它

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

2009-01-13 16:52 531 查看
转贴自:驱动开发网

原贴地址: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串口驱动源代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: