飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 2622|回复: 0

其它全局性的设备初始化操作

[复制链接]
  • TA的每日心情
    慵懒
    2019-3-12 17:25
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2010-9-23 18:47:30 | 显示全部楼层 |阅读模式

    在AddDevice中还需要加入其它一些步骤来初始化设备对象,下面我将按顺序描述这些步骤。

    初始化设备扩展

    设备扩展的内容和管理全部由用户决定。该结构中的数据成员应直接反映硬件的专有细节以及对设备的编程方式。大多数驱动程序都会在这里放入一些数据项,下面代码声明了一个设备扩展结构

    typedef struct _DEVICE_EXTENSION {    <--1
      PDEVICE_OBJECT DeviceObject;     <--2
      PDEVICE_OBJECT LowerDeviceObject;    <--3
      PDEVICE_OBJECT Pdo;      <--4
      UNICODE_STRING ifname;     <--5
      IO_REMOVE_LOCK RemoveLock;     <--6
      DEVSTATE devstate;      <--7
      DEVSTATE prevstate;
      DEVICE_POWER_STATE devpower;
      SYSTEM_POWER_STATE syspower;
    DEVICE_CAPABILITIES devcaps;     <--8
      ...
    } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
    • 我模仿DDK中官方的结构声明模式声明了这个结构
    • 我们可以用设备对象中的DeviceExtension指针定位自己的设备扩展。同样,我们有时也需要在给定设备扩展时能定位设备对象。因为某些函数的逻辑参数就是设备扩展本身(这里有设备每个实例的全部信息)。所以,我认为这里应该有一个DeviceObject指针
    • 我在一些地方曾提到过,在调用IoAttachDeviceToDeviceStack函数时,应该把紧接着你下面的设备对象的地址保存起来LowerDeviceObject成员用于保存这个地址
    • 有一些服务例程需要PDO的地址,而不是堆栈中某个高层设备对象的地址。由于定位PDO非常困难,所以最好的办法是在AddDevice执行时在设备扩展中保存一个PDO地址
    • 无论你用什么方法(符号连接或设备接口)命名你的设备,都希望能容易地获得这个名字。所以,这里我用一个Unicode串成员ifname来保存设备接口名。如果你使用一个符号连接名而不是设备接口,应该使用一个有相关含义的成员名,例如“linkname”。
    • 当你调用IoDeleteDevice删除这个设备对象时,需要使用一个自旋锁来解决同步安全问题,我将在第六章中讨论同步问题。因此,需要在设备扩展中分配一个IO_REMOVE_LOCK对象。AddDevice有责任初始化这个对象。
    • 你可能需要一个成员来记录设备当前的PnP状态和电源状态DEVSTATEPOWERSTATE枚举类型变量,我假设事先已经在头文件中声明了这些变量类型。我将在后面章节中讨论这些状态变量的用途。
    • 电源管理的另一个部分涉及电源能力设置的恢复,设备扩展中的devcaps结构用于保存这些设置

    下面是AddDevice中的初始化语句(着重设备扩展部分的初始化):

    NTSTATUS AddDevice(...)
    {
      PDEVICE_OBJECT fdo;
      IoCreateDevice(..., sizeof(DEVICE_EXTENSION), ..., &fdo);
      PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
      pdx->DeviceObject = fdo;
      pdx->Pdo = pdo;
      IoInitializeRemoveLock(&pdx->RemoveLock, ...);
      pdx->devstate = STOPPED;
      pdx->devpower = PowerDeviceD0;
      pdx->syspower = PowerSystemWorking;
      IoRegisterDeviceInte**ce(..., &pdx->ifname);
      pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(...);
    }
    初始化默认的DPC对象

    许多设备使用中断来报告操作完成。我将在第七章“读写数据”中讨论中断处理,其中对中断服务例程(ISR)能做什么做了严格的限定。特别是ISR不能调用用于报告IRP完成的例程(IoCompleteRequest)。利用DPC(推迟过程调用)可以绕过这个限制。你的设备对象中应包含一个辅助DPC对象,它可以调度你的DPC例程,该对象应该在设备对象创建后不久被初始化。

    NTSTATUS AddDevice(...)
    {
      IoCreateDevice(...);
      IoInitializeDpcRequest(fdo, DpcForIsr);
    }
    设置缓冲区对齐掩码

    执行DMA([size=-1]DirectMemoryAccess,直接内存存取传输的设备直接使用内存中的数据缓冲区工作。HAL要求DMA传输中使用的缓冲区必须按某个特定界限对齐,而且设备也可能有更严格的对齐需求。设备对象中的AlignmentRequirement表达了这个约束,它是一个位掩码,等于要求的地址边界减一。下面语句可以把任何地址圈入这个界限:

    PVOID address = ...;
    SIZE_T ar = fdo->AlignmentRequirement;
    address = (PVOID) ((SIZE_T) address & ~ar);

    还可以把任意地址圈入下一个对齐边界:

    PVOID address = ...;
    SIZE_T ar = fdo->AlignmentRequirement;
    address = (PVOID) (((SIZE_T) address + ar) & ~ar);

    在这两段代码中,我使用了SIZE_T把指针类型(它可以是32位也可以是64位,这取决于编译的目标平台)转化成一个整型,该整型与原指针有同样的跨度范围。

    IoCreateDevice把新设备对象中的AlignmentRequirement域设置成HAL要求的值。例如,Intel的x86芯片没有对齐需求,所以AlignmentRequirement的默认值为0。如果设备需要更严格的缓冲区对齐(例如设备有总线主控的DMA能力,要求对齐数据缓冲区),应该修改这个默认值,如下:

    if (MYDEVICE_ALIGNMENT - 1 > fdo->AlignmentRequirement)
      fdo->AlignmentRequirement = MYDEVICE_ALIGNMENT - 1;

    我假设你在驱动程序某处已定义了一个名为MYDEVICE_ALIGNMENT的常量,它是2的幂,代表设备的数据缓冲区对齐需求。

    其它对象

    设备可能还有其它一些需要在AddDevice中初始化的对象。这些对象可能包括各种同步对象各种队列头(queue anchors),聚集/分散列表缓冲区,等等。事实上,在本书的其它地方讨论这些对象的初始化更合适。

    初始化设备标志

    设备对象中有两个标志位需要在eAddDevic初始化,并且它们在以后也不会改变,它们是DO_BUFFERED_IO和DO_DIRECT_IO标志。你只能设置并使用其中一个标志,它将决定你以何种方式处理来自用户模式的内存缓冲区。(我将在第七章中讨论这两种缓冲模式的不同,以及你如何选择) 由于任何在后面装入的上层过滤器驱动程序将复制你的标志设置,所以在AddDevice中做这个选择十分重要。如果你在过滤器驱动程序装入后改变了设置,它们可能会不知道。

    设备对象中有两个标志位属于电源管理范畴。与前两个缓冲区标志不同,这两个标志在任何时间都可以被改变。我将在第八章中详细讨论它们,但这里我先介绍一下。DO_POWER_PAGABLE意味着电源管理器将在PASSIVE_LEVEL级上向你发送IRP_MJ_POWER请求DO_POWER_INRUSH意味着你的设备在上电时将汲取大量电流,因此,电源管理器将确保没有其它INRUSH设备同时上电

    设置初始电源状态

    大部分设备一开始就进入全供电状态。如果你知道你的设备的初始电源状态,应该告诉电源管理器:

    POWER_STATE state;
    state.DeviceState = PowerDeviceD0;
    PoSetPowerState(fdo, DevicePowerState, state);

    电源管理的细节请见第八章。

    建立设备堆

    每个过滤器驱动程序和功能驱动程序都有责任把设备对象放到设备堆栈上,从PDO开始一直向上。你可以调用IoAttachDeviceToDeviceStack完成你那部分工作:

    NTSTATUS AddDevice(..., PDEVICE_OBJECT pdo)
    {
      PDEVICE_OBJECT fdo;
      IoCreateDevice(..., &fdo);
      pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo, pdo);
    }

    IoAttachDeviceToDeviceStack的第一个参数是新创建的设备对象的地址。第二个参数是PDO地址AddDevice的第二个参数也是这个地址。返回值是紧接着你下面的任何设备对象的地址,它可以是PDO,也可以是其它低级过滤器设备对象。如果该函数失败则返回一个NULL指针,因此你的AddDevice函数也是失败的,应返回STATUS_DEVICE_REMOVED

    清除DO_DEVICE_INITIALIZING标志

    AddDevice中最后一件需要做的事是清除设备对象中的DO_DEVICE_INITIALIZING标志

    fdo->Flags &= ~DO_DEVICE_INITIALIZING;

    当这个标志设置时,(主)I/O管理器(谓)将拒绝(宾)任何打开该设备句柄的请求向该设备对象上附着其它设备对象的请求。在驱动程序完成初始化后必须清除这个标志。在以前版本的Windows NT中,大部分驱动程序在DriverEntry中创建所有需要的设备对象。当DriverEntry返回时,I/O管理器自动遍历设备对象列表并清除该标志。但WDM驱动程序中设备对象在DriverEntry返回后才创建,所以I/O管理器不会自动清除这个标志,驱动程序必须自己清除它

    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表