引言:
总线驱动是设计用来控制和配置特殊的总线,同时会控制和配置总线上的硬件,通过client驱动形式来加载、卸载总线上的设备驱动。也负责client驱动对总线的请求。从软件结构上,总线驱动分为2种。
1是为client驱动服务
2是配置、加载然后控制总线的client驱动。
微软提供了大部分的总线驱动,如PCI、PCCARD和root总线。
总线驱动的作用:
总线驱动的作用包括:硬件配置、硬件电源控制、总线地址转换和加载、卸载上层的client驱动。
大多数的上层client驱动,通过总线驱动调用ActivateDevice(Ex)来加载的。ActivateDevice(Ex)通过调用者传递的注册表内容,来加载client驱动。
也就是说加载驱动,必须使用注册表。总线驱动除了有固定的注册表值,运行时也会动态的写入信息到注册表中。
对PNP的驱动来说,总线驱动配置硬件,根据PNP的信息建立注册表信息。然后通过ActivateDevice(Ex)来加载client驱动,和创建新的注册表信息。
对于非PNP的驱动和中间驱动来说,注册表的内容是一开始设定好的。可能是微软、OEM或第三方提供总线驱动的厂商,提供的注册表内容。这类型的总线驱动,也是会使用注册表加载其他驱动。
总线驱动可以被parent总线驱动加载起来,创建一个总线的树。Root总线和这个树的主干除外。Root总线驱动通过注册表路径找到注册表内容,被Device Manager加载起来。如下:
[HKEY_LOCAL_MACHINE/Drivers]
"RootKey"="Drivers/BuiltIn"
微软已经设定好默认的注册表值。当然,OEM也可以修改root总线驱动的注册路径,来枚举设备驱动。
上面的例子把root驱动设定在[HKEY_LOCAL_MACHINE/Drivers]的路径中。Device Manager在启动过程中,会加载总线驱动。
注意:Device Manager在系统具有2次启动流程的过程中,会让root总线驱动运行2次。
第一次的启动过程,Device Manager删除了驱动的Active键值,来停止驱动的活动。然后把root总线驱动用BuiltInPhase1的名字,加载起来。如果系统有2此启动流程的话,当系统Phase 2的事件被设置后,Device Manager会再次加载root总线驱动。Device Manager第二次,就会通过注册表里的总线名字来加载了。
总线驱动访问和CEDDK总线函数:
Client驱动使用总线驱动来
1、 在次级总线和parent总线上,做总线地址的转换。
2、 设备电源状态的改变
3、 访问配置数据内容
使用总线驱动的原因有很多,一个最重要的原因是client驱动从总线上分离出来。这意味着驱动可以运行在用户态,或者在不同的总线上使用同一个client驱动。例如COM16550和NE2000的驱动。它们能在PCI总线,或者本地总线和PCMCIA总线上,使用同一个二进制形式的驱动(已经编译好的驱动)。
总线访问函数:
为了访问到parent总线驱动,client驱动需要调用CreateBusAccessHandle函数来获取总线驱动的访问句柄。通常是clinet初始化时候调用的(xxx_init),因为client驱动需要传入一个有效的注册表路径。CreateBusAccessHandle调用后,需要CloseBusAccessHandle结束使用。通常在xxx_deinit退出前,调用CloseBusAccessHandle。详细参考MSDN介绍。
当总线访问句柄被创建后,client驱动可以通过句柄使用其他功能函数。
CEDDK中的总线驱动函数,相当于是封装过IO control函数。微软极力推荐使用CEDDK的总线函数,而不是直接通过IO control函数去调用驱动。总线IO control函数会经常修改,但CEDDK中的不会变动。
下面列出CEDDK功能函数和对应的IO control函数。
CreateBusAccessHandle
|
Creates an access handle for other CEDDK Bus functions. This results in the Parent bus’ Open() entry being called. |
CloseBusAccessHandle
|
Closes the handle which was created by using CreateBusAccessHandle(). This results in the Parent bus’ Close() entry called. |
SetDevicePowerState |
IOCTL_BUS_SET_POWER_STATE |
GetDevicePowerState |
IOCTL_BUS_GET_POWER_STATE |
TranslateBusAddr |
IOCTL_BUS_TRANSLATE_BUS_ADDRESS |
TranslateSystemAddr |
IOCTL_BUS_TRANSLATE_SYSTEM_ADDRESS |
SetDeviceConfigurationData |
IOCTL_BUS_SET_CONFIGURE_DATA |
GetDeviceConfigurationData |
IOCTL_BUS_GET_CONFIGURE_DATA |
GetChildDeviceRemoveState |
IOCTL_BUS_IS_CHILD_REMOVED |
SetDevicePowerState和GetDevicePowerState,是client驱动向parent总线驱动进入、查询某个电源状态。虽然某些client驱动自己处理电源状态,但让client驱动变更电源状态的动作应该由总线驱动负责。当总线驱动加载上一个client,会把电源状态设置在D0.当总线驱动卸载client前,会把硬件电源状态设置在D4。
TranslateBusAddr和TranslateSystemAddr,是把CPU的物理地址转换为子总线上的地址。为此,TranslateBusAddr需要把目标地址从子总线上转换成parent总线。这个动作会一直传递到root总线上,root总线会调用OAL函数HalTranslateBusAddress来获取CPU能访问到的真实物理地址。
TranslateSystemAddr类似,但是形式不一样。OAL使用HalTranslateSystemAddress把CPU地址转换成总线地址。所以,多层的总线的地址转换,可以通过这2函数来完成。
在虚拟和静态系统地址映射到驱动前,BusTransBusAddrToVirtual和BusTransBusAddrToStatic使用TranslateBusAddr函数,把总线地址转换成CPU地址。
CEDDK不支持的IO control函数:
CEDDK提供BusIoControl和 BusChildIoControl给client驱动使用。Client驱动可以用这2函数,直接调用总线的IO control。不同在于BusChildIoControl可以传递给client驱动。
IOCTL_BUS_ACTIVATE_CHILD和IOCTL_BUS_DEACTIVATE_CHILD,是CEDDK提供一组没封装的函数。任何应用程序打开了一个总线驱动的句柄后,都可以通过这个方式去使用IO control。这2个函数可以控制client驱动的active和deactivate。注意并不是所有的client驱动可以deactivate。
总线驱动名字的分配
Client总线驱动的名字是由parent总线驱动分配的。总线名字组成样例如下:
Busname根据总线驱动在注册表下的BusName键值决定的。是微软或OEM预先定义好了。
Bus#、device#和function#是根据client驱动在注册表中,BusNumber、DeviceNumber和FunctionNumber等键值决定的。非PNP总线(如root总线)中,bus#是有总线设备驱动的注册表值决定的。而device#和function#,则是由总线驱动自动分配的。
因为busname是系统唯一的名字,系统有多个总线情况下,需要选择一个合适的总线名字。样例如下:
1、Root总线驱动——BuildIn
2、PIC总线驱动——PCI
3、PC Card总线驱动——PCCARD
支持电源管理的驱动和总线驱动的电源管理
支持电源管理的驱动有2中类型。1是由Power manager管理的,2是设备驱动自己管理。受PM管理的驱动有以下的IO control选项:
IOCTL_POWER_CAPABILITIES:PM通过这个接口查询设备支持的电源状态。如果驱动返回是支持D0-D4的状态,就可以使用IOCTL_POWER_SET来设置设备的电源状态。如果不接受电源状态的改变,那么PM把这个当做服务的错误。
IOCTL_POWER_QUERY:设备返回当前的电源状态
IOCTL_POWER_SET:PM使用这个选项来设置设备的电源状态。PM只会设置从IOCTL_POWER_CAPABILITIES得到的电源状态,而不会任意的设置。
Client驱动在PM中的流程:
如上所提,client总线驱动负责改变硬件的电源状态。那似乎PM最好就是通过总线驱动之间去管理电源。实际上并不如此,原因有2个:
1、 client驱动需要知道当前的电源状态,才能正确的操作。Client驱动关闭硬件前,需要知道硬件操作已经完成,或者不会再有硬件操作请求了。
2、 CE5之前的驱动,自己控制电源。为了后兼容性,PM需要继续直接使用client驱动。首先电源控制命令从PM发送到client驱动,通过client总线驱动提供的总线IO control去操作。PM调用DeviceIOControl(IOCTL_POWER_SET),来设置client驱动。其次client驱动调用CEDDK的SetDevicePowerState函数,把IOCTL_BUS_SET_POWER_STATE传递到总线驱动。最后,由总线驱动来决定怎样调用client驱动。
驱动同时处理IOControl、读写和电源管理的操作,是比较困难的。当这些请求同时发生,需要使用一些同步手段,保证运行正确。使用CEDDK的函数,可以方便的处理这个情况:
Init()
{
hPwrHandle = DDKPwr_Initialize(__in PFN_SETPOWERLEVEL
pSetPowerLevelFn,
__in DWORD dwContext,
__in BOOL fAbortOnPMRequests,
__in DWORD dwTimeout );
};
Deinit
{
DDKPwr_Deinitialize(hPwrHandle);
}
Function_Request()
{
HANDLE hLevelHandle = DDKPwr_RequestLevel(hPwrHandle,
__in CEDEVICE_POWER_STATE dx );
… // Do some work.
DDKPwr_ReleaseLevel(hPwrHandle, hLevelHandle);
}
PowerMgr_Request(Dx)
{ // From Power IO Control
DDKPwr_SetDeviceLevel(hPwrHandle ,Dx , __in PFN_SETPOWERLEVELCALLBACK pCallbackFn );
}
以上是一个典型的使用示例,public/common/oak/drivers/serial/serpddcm/cserpdd.cpp有具体例子说明如何使用。
总线驱动库
微软提供了总线驱动库去使用总线。CEDDK会传递总线的IO Control到总线驱动。这有2中代码有不同,1是传递到总线驱动,2是传递到client驱动。
Class DefaultBusDriver {
Public:
// Constructor
// Client Driver Specific function.
// Bus Driver Function.
Protected:
// Container for all Folders of Child Client Driver.
// Child Folder Class Manufacture.
};
class DeviceFolder {
public:
// Constructor
// Device Power Function
// Device Configuration Function
// Device Bus Address Translation Function
// Device Driver Load & Unload Function.
};
以上是总线驱动的抽象类模板。总线驱动继承这2个类,再实现自己特殊虚函数。总线驱动库没有提供了这2个类的实例,需要自己创建自己的子类。
总线驱动库提供了一些功能:
DefaultBusDriver类
1、 为驱动提供folder
2、 管理DeviceFolder容器
3、 通过IOCTL_BUS_TRANSLATE_SYSTEM_ADDRESS 和IOCTL_BUS_TRANSLATE_BUS_ADDRESS,提供缺省的总线转换功能。如果是root总线驱动,parent总线驱动转换地址会通过HalTranslateBusAddress 或者 HalTranslateSystemAddress实现。
4、 处理基本的IOCTL_BUS_IS_CHILD_REMOVED, IOCTL_BUS_NAME_PREFIX和其他IO Control函数。
5、 提供PostInit虚函数。
6、 提供IOCTL_BUS_POSTINIT来调用PostInit。
DeviceFolder
1、 包括为驱动加载、卸载,创建缺省总线名字的功能。
2、 通过IOCTL_BUS_GET_CONFIGURE_DATA 和 IOCTL_BUS_SET_CONFIGURE_DATA,为DefaultBusDriver类提供缺省的配置功能。当由PCI client驱动有请求,调用HalGetBusDataByOffset 或 HalSetBusDataByOffset。
3、当DefaultBusDriver通过IOCTL_BUS_SET_POWER_STATE设置电源,提供虚函数给SetPowerState。
PCI总线驱动:
PCI总线驱动复制配置PCI总线,访问资源,根据模板查找驱动,加载驱动和注册表。这里主要说明外部client调用Bus IOControl的细节。
为使支持client驱动通过Bus IOControl调用到PCI总线驱动,下面是总线驱动库的一个子类。
class PciDeviceFolder : public DeviceFolder{
virtual BOOL PostInit() ;
…
};
class PciBusEnum : public DefaultBusDriver {
…
};
PciBusEnum提供PostInit函数去重构parent类的函数。这个类有2个子功能函数:
1、 AssignChildDriver是为每个驱动实例创建PciDeviceFolder,同时会把DeviceFolder放到Device Folder容器中。
2、 ActivateAllChildDriver调用DeviceFolder::LoadDevice,去加载驱动。
PciDeviceFolder提供了SetPowerState去重构parent类的函数,这个调用过程依循PCI PM 1.1的协议。
Root总线驱动:
缺省的root总线驱动是BusEnum类,由总线驱动库DefaultBusDriver类中衍生而出。没有修改DeviceFolder的部分,因此不支持使用SetPowerState函数。
class BusEnum : public DefaultBusDriver {
virtual BOOL PostInit() ;
…
};
BusEnum提供了PostInit函数,去重构它的parent类。新的函数有2个子功能函数:
1、AssignChildDriver使用注册表键值,为每个Device实例创建一个DeviceFolder。会把DeviceFolder放到Device Folder容器中。
2、ActivateAllChildDriver调用DeviceFolder::LoadDevice,去加载驱动。
Root总线需要给client驱动提供电源状态支持的话,SetPowerState Driver Folder功能一定要提供。OMAP850有个的样例,展示了平台root总线驱动是怎样运作的。