总线驱动

引言:

总线驱动是设计用来控制和配置特殊的总线,同时会控制和配置总线上的硬件,通过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>_<bus#>_<device#>_<function#>

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总线驱动是怎样运作的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值