来源于《Windows CE工程实践完全解析》
串口驱动程序PDD层初始化任务的主要是CPDD2416Uart类及其继承类的构造函数、Init和PostInit成员函数,它们都被串口驱动程序MDD层的COM_Init函数调用。
在被SerInit函数被调用的CreateSerialObject函数中,会根据输入参数DeviceArrayIndex的数值产生一个串口驱动程序PDD类的实例:
CSerialPDD * pSerialPDD = NULL;
switch (DeviceArrayIndex)
{
case 0:
pSerialPDD = new CPdd2416Serial0(lpActivePath,pMdd, pHwObj);
break;
case 1:
pSerialPDD = new CPdd2416Serial1(lpActivePath,pMdd, pHwObj);
break;
case 2:
pSerialPDD = new CPdd2416Serial_IrDA(lpActivePath, pMdd, pHwObj);
break;
case 3:
pSerialPDD = new CPdd2416Serial3(lpActivePath, pMdd, pHwObj);
Break;
}
用new操作符生成一个PDD类。实例隐含了执行用户PDD类的构造函数,并且子类的构造函数会连锁地导致其父类的构造函数被执行。比如,CPdd2416Serial1类的构造函数执行时会调用CPdd2416Uart类构造函数。CPdd2416Uart类构造函数又会调用CSerialPDD类的构造函数。构造函数只是类的数据成员赋值初始值,对串口硬件的初始化工作都留给了Init成员函数和PostInit成员函数。
Init成员函数紧接着类构造函数之后被执行:
if (pSerialPDD && !pSerialPDD->Init())
{
delete pSerialPDD;
pSerialPDD = NULL;
}
这里的pSerialPDD 指针变量不是CSerialPDD类型,而是由DeviceArrayIndex参数指定的具体串口驱动PDD类类型。以DeviceArrayIndex取值为0为例。pSerialPDD 指针的类型是cPdd2416Serial1。同构造函数一样,cPdd2416Serial1类的Init函数也会连锁地调用CPdd2416Uart类和CSerialPDD类的Init成员函数。
对于init函数。
virtual BOOL Init()
{
PHYSICAL_ADDRESS ioPhysicalBase = { S3C2416_BASE_REG_PA_IOPORT, 0};
ULONG inIoSpace = 0;
if (TranslateBusAddr(m_hParent,Internal,0, ioPhysicalBase,&inIoSpace,&ioPhysicalBase))
{
// Map it if it is Memeory Mapped IO.
m_pIOPregs =(S3C2416_IOPORT_REG *) MmMapIoSpace(ioPhysicalBase, sizeof(S3C2416_IOPORT_REG),FALSE);
}
if (m_pIOPregs)
{
DDKISRINFO ddi;
if (GetIsrInfo(&ddi)== ERROR_SUCCESS && KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &ddi.dwIrq, sizeof(UINT32), &ddi.dwSysintr, sizeof(UINT32), NULL))
{
RegSetValueEx(DEVLOAD_SYSINTR_VALNAME,REG_DWORD,(PBYTE)&ddi.dwSysintr, sizeof(UINT32));
}
else
{
return FALSE;
}
// TXD1(GPH2), RXD1(GPH3), RTS1(GPH11), CTS1(GPH10)
m_pIOPregs->GPHCON &= ~(0x3<<4 | 0x3<<6 | 0x3<<22);
m_pIOPregs->GPHCON |= (0x2<<4 | 0x2<<6 | 0x2<<22);
m_pIOPregs->GPHUDP &= ~( 0x1<<(4) | 0x1<<(6) | 0x1<<(22) );
return CPdd2416Uart::Init();
}
return FALSE;
};
对于这个函数,首先映射串口的I/O端口地址:
PHYSICAL_ADDRESS ioPhysicalBase = { S3C2416_BASE_REG_PA_IOPORT, 0};
ULONG inIoSpace = 0;
if (TranslateBusAddr(m_hParent,Internal,0, ioPhysicalBase,&inIoSpace,&ioPhysicalBase))
{
// Map it if it is Memeory Mapped IO.
m_pIOPregs =(S3C2416_IOPORT_REG *) MmMapIoSpace(ioPhysicalBase, sizeof(S3C2416_IOPORT_REG),FALSE);
}
CPdd2416Serial1类虽然命名为Serial1,但是它实际是对应于S3C241芯片上的UART0端口的PDD类。以上这段代码的作用就是把这个地址映射到调用COM_Init的设备管理器所在的进程(也就是Windows CE操作系统内核进程nk.exe)
的虚拟地址空间中,最终转换所得的虚拟地址存放在CPdd2416Serial1类的成员变量m_pIOPregs中。
TranslateBusAddr函数是一个CEDDK函数,它的功能是将一个总线相对物理地址映射成一个系统物理地址。该函数原型是这样子的:
BOOL TranslateBusAddr(HANDLE hBusAccess,INTERFACE_TYPE InterfaceType,ULONG BusNumber,
PHYSICAL_ADDRESS BusAddress,PULONG AddressSpace,PPHYSICAL_ADDRESS TranslatedAddress);
第一个参数hBusAccess是父总线的设备句柄。就是连接外设与CPU的总线句柄。Init函数调用TranslateBusAddr函数时给这个参数的实际参数是从CSerialPDD类继承的数据成员m_hParent,
这个成员在CSerialPDD类的构造函数中这样子被赋值:
m_hParent = CreateBusAccessHandle(lpActivePath);
CreateBusAccessHandle函数也是一个CEDDK函数。
TranslateBusAddr函数的返回值是BOOL类型。
第二个被调用的CEDDK函数MmMapIoSpace函数用于把一个系统物理地址映射到当前的执行流所在的进程的虚拟地址空间中。它的原型如下:
PVOID MmMapIoSpace(PHYSICAL_ADDRESS PhysicalAddress,ULONG NumberOfByte,BOOLEAN CacheEnable);
PhysicalAddress参数是待转换的系统物理地址;NumberOfByte指出PhysicalAddress参数所代表的存储区域的以字节为单位的长度的单位;CacheEnable表示转换所得的虚拟地址是否是可缓存的。
请注意:这里被TranslateBusAddr和MmMapIoSpace函数转换的是GPIO控制器的SFR基地址。
在地址转换成功后,下一步是查询出串口UART0端口设备的逻辑中断号、并且把它记录在Active注册表中:
DDKISRINFO ddi;
if (GetIsrInfo(&ddi)== ERROR_SUCCESS && KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &ddi.dwIrq, sizeof(UINT32), &ddi.dwSysintr, sizeof(UINT32), NULL))
{
RegSetValueEx(DEVLOAD_SYSINTR_VALNAME,REG_DWORD,(PBYTE)&ddi.dwSysintr, sizeof(UINT32));
}
else
{
return FALSE;
}
随后的CPdd2416Serial1类成员函数Init的代码为几个重要的数据成员赋值:
// TXD1(GPH2), RXD1(GPH3), RTS1(GPH11), CTS1(GPH10)
m_pIOPregs->GPHCON &= ~(0x3<<4 | 0x3<<6 | 0x3<<22);
m_pIOPregs->GPHCON |= (0x2<<4 | 0x2<<6 | 0x2<<22);
m_pIOPregs->GPHUDP &= ~( 0x1<<(4) | 0x1<<(6) | 0x1<<(22) );
最后
CPdd2416Serial1类的Init函数要调用其父类CPdd2416Uart的Init函数。并且以它的返回值作为自己返回值:
return CPdd2416Uart::Init();
CPdd2416Uart类的Init函数全部代码都要在以下条件的判断成立时才被执行:
if ( CSerialPDD::Init() && IsKeyOpened() && m_XmitFlushDone!=NULL)
这当中包括执行CSerialPDD类的Init函数,它主要负责初始化串口设备驱动的电源管理。CSerialPDD类的Init函数返回值TRUE可以保证IsKeyOpended函数也返回TRUE。它表示串口UART0的Active注册表键是被打开的。OEM用户在以后的驱动代码中就可以放心的调用从CRegistryEdit类继承来的成员函数。在CPdd2416Uart类的Init成员函数执行代码的最后两个重要的步骤:
if (!MapHardware() || !CreateHardwareAccess()) {
return FALSE;
}
调用MapHardware和CreateHardwareAccess函数。并且这两个函数必须返回TRUE表示执行成功。他们都是CPdd2410Uart类的成员函数。但从代码上看,MapHardware函数的功能是为两个数据成员 m_pRegVirtualAddr和m_pINTregs赋值,他们分别是UART0端口和中断控制器SFR寄存器的片内地址映射到Windows CE内核进程的地址空间的虚拟地址。之所以有m_pINTregs成员是因为在PDD中启动的IST线程需要操作中断寄存器。
MapHardware函数实现地址映射的方法和前面的CPdd2416Serial1类的Init函数一样。先使用TranslateBusAddr函数将总线相对地址映射称系统物理地址。再使用MmMapIoSpace函数把
系统物理地址转换成内核进程的虚拟地址。但是获取UART0的总线相对地址的做法却比较特殊:
// Get IO Window From Registry
DDKWINDOWINFO dwi;
if ( GetWindowInfo( &dwi)!=ERROR_SUCCESS ||
dwi.dwNumMemWindows < 1 ||
dwi.memWindows[0].dwBase == 0 ||
dwi.memWindows[0].dwLen < 0x30) //0x2c)
return FALSE;
GetWindowInfo函数同GetIsrInfo函数相似,它是封装了CEDDK函数DDKReg_GetWindowsInfo的CRegistryEdit类的成员函数,用于根据串口驱动程序的Active注册表键获取得串口的I/O
窗口数据。本来对于UART0端口,也可以向中断控制器那样直接给出总线物理地址,但是CPdd2416Uart类本来就是个抽象类,他要同时为S3C2416芯片上的3个串口服务。所以在被CPdd2416Uart类的
Init函数调用的
MapHardware
函数不能就某一个具体的串口给出物理地址。最好的办法就是用
GetWindowInfo
函数从系统注册表中获取。
最后一个函数
CreateHardwareAccess
,其职能就是根据在
MapHardware函数中获得的串口的SFR寄存器集在Windows CE内核进程中的虚拟地址m_pRegVirtualAddr构造一个CReg2416Uart类实例。并且将指针保存在数据成员m_pReg2416UART中。这个类
很容易让人想到CRegistryEdit类,不过值得庆幸的是,
CReg2416Uart类名的Reg指的是SFR寄存器而不是Registry注册表。而且他的实例是CPdd2416Uart类的数据成员。他与CPdd2416Uart类没有继承关系。紧接着被调用的CReg2416Uart类的
Init函数只是部分寄存器写入初始化从而将串口控制寄存器硬件置于一个可知的稳定的工作状态。