NDIS Miniport驱动框架介绍
NDIS miniport adapter 类继承了 KNdisMiniAdapter 类 并且声明了处理程序所需的signatures,例如Initialize(), Halt()和 Reset()。它们可以是虚拟的或者非虚拟的,或是派生类内联成员。对于这些处理程序的命名和DDK很接近。例如Initialize()对应MiniportInitialize(),Halt()对应MiniportHalt()。这些处理程序的处理过程和IRQL级与相应的DDK处理程序类似。
下面的UML类图表描述了DriverNetworks框架类和驱动开发者实现的类之间的关系。驱动开发者提供的类用黄颜色显示。
驱动开发者主要提供两个类
• KNdisMiniDriver-继承 driver 类
• KNdisMiniAdapter-继承 adapter 类
注意:Network Driver Wizard应用程序将为NDIS驱动项目自动生成这两个类。
在驱动安装之前,框架调用adapter的DriverEntry()方法,该方法将在NDIS中注册adapter。注册由帮助模块类KNDIS_MINIPORT_CHARACTERISTICS处理,它将一组adapter支持的处理程序提交给NDIS。默认的处理程序集合以及其他miniport特征都在帮助模块类中说明,KNdisAdapterTraits。每个miniport驱动项目都包括一个头文件Characteristics.h,这可以用来重新定义特殊adapter的某些特征(例如,处理程序集合)。
Adapter注册完成之后,NDIS开始调用adapter的处理程序。这些处理程序通过框架模块类KNdisWrapper从NDIS中调用。对每个adapter类A,框架都用具体的例子说明KNdisWrapper的实现,KNdisWrapper转发NDIS回调给正确的A定义的处理程序。驱动开发者不必实现QueryInformation()和SetInformation()处理程序,这两个程序用来处理NDIS OIDs。在DriverNetworks中,框架通过OID maps来实现对OID的处理。
任何一个KNdisMiniAdapter派生类的实体都由DriverNetworks框架自动创建。因此不必为adapter调用new()。框架负责向NDIS注册,解释原始NDIS的MiniportInitialize()的回调,并创建adapter类的一个实体。创建实体之后,框架将控制提交给派生类的Initialize()处理程序。Initialize()处理程序执行必要的定制步骤,例如声明硬件资源和NIC初始化。从Initialize()处理程序成功的返回,adapter才能被系统使用。
KNdisMiniAdapter派生对象的消除也由框架处理。在消除之前立即调用Halt()处理程序。Adapter类执行硬件特性的shutdown,资源回收等等。当miniport通过控制面板或者系统shutdown时,才调用Halt()处理程序。
NDIS Miniport Driver Handler Signatures
class CMyAdapter : public KNdisMiniAdapter {
public:
// Initialization and shutdown
NDIS_STATUS Initialize(IN OUT KNdisMedium& Medium, IN KNdisConfig& Config) ;
VOID Halt(VOID);
NDIS_STATUS Reset(OUT PBOOLEAN AddressingReset);
void Shutdown(VOID);
// OID requests
NDIS_STATUS QueryInformation(
IN NDIS_OID Oid,
IN PVOID InformationBuffer,
IN ULONG InformationBufferLength,
OUT PULONG BytesWritten,
OUT PULONG BytesNeeded
) ;
NDIS_STATUS SetInformation(
IN NDIS_OID Oid,
IN PVOID InformationBuffer,
IN ULONG InformationBufferLength,
OUT PULONG BytesRead,
OUT PULONG BytesNeeded
) ;
// Sending packets
VOID SendPackets(
IN PPNDIS_PACKET PacketArray,
IN UINT NumberOfPackets
) ;
VOID ReturnPacket(IN PNDIS_PACKET Packet);
// overridables for interrupt-driven miniports
BOOLEAN CheckForHang() ;
VOID DisableInterrupt() ;
VOID EnableInterrupt() ;
VOID HandleInterrupt() ;
VOID Isr(OUT PBOOLEAN InterruptRecognized,
OUT PBOOLEAN QueueMiniportHandleInterrupt ) ;
};
提示: 最容易熟悉框架的途径就是启动Network Driver Wizard生成一个基本 NDIS miniport 或者NDIS IM 驱动项目。
DriverNetwork miniport驱动学习(2)
声明硬件资源
Windows NT和Windows 2000定义了四种基本外围设备的硬件资源,例如NIC:
• I/O Ports
• Interrupt
• Memory
• DMA
枚举类型CM_RESOURCE_TYPE定义在ndis.h文件中,用于确定资源类型。
每一类资源都具有CM_PARTIAL_RESOURCE_DESCRIPTOR 结构描述的一组属性,该结构也定义在ndis.h文件中。例如, I/O端口属性包括I/O端口基地址,I/O端口长度范围等等。
DriverNetworks将硬件资源抽象成KNdisResource< CM_RESOURCE_TYPE>模块,提供了查询资源属性的访问方法。
NDIS Miniport 驱动处理硬件资源一般有以下三步:
1) Query the system 查询系统有关分配给miniport的硬件资源信息。
2) Query the attributes 查询每个资源的属性。
3) Initialize system objects 基于属性对系统对象初始化(例如, Interrupt 和I/O端口范围) 。
1.查询系统有关分配给miniport的硬件资源信息
根据NDIS版本和NIC总线类型,查询系统有许多不同的方法。NDIS4驱动采用总线特有的请求来完成Pre-PnP。DriverNetworks提供了两个类来完成该过程:KNdisPciResourceRequest和KNdisParamResourceRequest。
在NDIS 5中,PnP管理器处理所有硬件资源,包括non-PnP设备。对于NDIS 5驱动,系统采用非总线特有的KNdisPnpResourceRequest来查询,这是基于NdisMQueryAdapterResources的。尽管所有其他的NDIS 5资源请求类都很好用,但是一般还是习惯用KNdisPnpResourceRequest来查询硬件资源。
一般在Initialize()处理程序中对正确的请求初始化来实现系统查询。 例如:
KNdisPciResourceRequest req(this);
2.查询属性和初始化系统对象
在无论采用什么查询方法查询系统之后,接下来就要完成属性查询和初始化系统对象。 访问每一个资源都要求对正确资源类型初始化。例如:
KNdisResource nt(req);
上面初始化一个中断资源描述符。
当系统对象初始化后,可以利用资源属性创建一个正确的系统对象。例如:
m_pInterrupt = new KNdisInterrupt(this,Int.Level(), Int.Affinity());
通过IsValid()方法检查错误,来确保资源对驱动来说是可用的。下面的代码示范了获得中断和I/O端口资源的技术:
NDIS_STATUS MyAdapter::Initialize(KNdisMedium& Medium, IN KNdisConfig& Config)
{. . .
// get h/w resources
KNdisPnpResourceRequest request(Config); // how do we query
KNdisResource Port(request); // what do we query
KNdisResource Int(request); // what do we query
// Make sure the resources are available
if (!Port.IsValid())
KNDIS_RETURN_ERROR (NDIS_STATUS_NOT_ACCEPTED);
if (!Int.IsValid())
KNDIS_RETURN_ERROR (NDIS_STATUS_NOT_ACCEPTED);
// Register i/o port range system object:
m_Ports.Initialize(this, Port);
if (!m_Ports.IsValid())
KNDIS_RETURN_ERROR (NDIS_STATUS_RESOURCES);
// Register interrupt system object:
m_Interrupt.Initialize(this, Int, NdisInterruptLatched);
if (!m_Interrupt.IsValid())
KNDIS_RETURN_ERROR (NDIS_STATUS_RESOURCES);
. . .
// OK!
return NDIS_STATUS_SUCCESS;
}
DriverNetwork miniport驱动学习(8)
NDIS Miniports实现OID处理
NDIS采用对象标示符OID,在DDK的ntddndis.h中定义,重新定位和设置NDIS miniport驱动的各种配置和操作参数。每一个OID代表了一个32位值,与正确的数据结构一起通过MiniportQueryInformation 和MiniportSetInformation处理程序提交给驱动。
DDK类型NDIS驱动一般都要实现OID处理过程,通过MiniportQueryInformation和MiniportSetInformation内一个比较长的switch statement。另一方面,DriverNetworks开发了一种消息映射方法,将每个OID当作一个消息由特定的OID处理程序处理。
OID消息映射方法允许你将OID请求分析和特殊OID处理过程分开,这样就增强了可读性和可维护性。
1.OID 消息映射
OID 消息映射就是一组宏状态声明支持某种NDIS OIDs并实现OID消息解码cracker 。例如:
// "查询" 映射
BEGIN_OID_QUERY_MAP(MyAdapter)
OID_QUERY_ENTRY (OID_GEN_HARDWARE_STATUS)
OID_QUERY_ENTRY (OID_GEN_MEDIA_SUPPORTED)
. . .
END_OID_QUERY_MAP
// "设置"映射
BEGIN_OID_SET_MAP(MyAdapter)
OID_SET_ENTRY( OID_GEN_CURRENT_LOOKAHEAD)
OID_SET_ENTRY( OID_GEN_CURRENT_PACKET_FILTER)
OID_SET_ENTRY( OID_GEN_PROTOCOL_OPTIONS)
. . .
END_OID_SET_MAP()
NDIS miniport项目包含了两个映射:一个是查询请求,一个是设置请求。 每一个OID映射就是一块在BEGIN_OID_MAP和END_OID_MAP宏状态之间的宏块组成,对应着MyAdapter::QueryInformation()和MyAdapter::SetInformation()处理程序的实现。Network Driver Wizard生成的NDIS Miniport项目包括含有映射定义的XxxOids.cpp文档。
在映射中,OID_QUERY_ENTRY (or OID_SET_ENTRY) 声明支持对特定的OID。因此编译器能生成代码来激活一个相应的OID处理程序。
OID消息映射在内部可以执行必要的样本文件OID分析代码,例如检查输入/输出缓冲大小匹配。OID映射可以实现为一种C switch statement,它与DDK miniport驱动的运行效率一样。
2.OID 处理
DriverNetworks OID处理程序是KNdisMiniAdapter派生类的一个成员函数,用来处理特殊的NDIS OID请求。OID处理程序由OID消息映射中激活。 DriverNetworks 定义了OID处理程序的命名习惯和函数原型。对于OID 常量OID_XXX_YYY的命名习惯是: getOID_XXX_YYY() 是查询信息请求; setOID_XXX_YYY()是设置信息请求。
OID查询(获得)处理程序的三种有效形式:
1) short property(返回一个T类型的值):
T getOID_XXX_YYY ();
2) short handler(返回NDIS_STATUS,如果成功――T类型的值)
NDIS_STATUS getOID_XXX_YYY (T* pValue);
3) variable-size handler(返回NDIS_STATUS,如果成功――变量矩阵)
NDIS_STATUS getOID_XXX_YYY(T* pValue,IN OUT PULONG ByteCount, OUT PULONG ByteNeeded);
OID设置处理程序的两种有效形式:
1)short property(接收一个T类型值,返回NDIS_STATUS)
NDIS_STATUS setOID_XXX_YYY (T* pValue);
2)variable handler(接收一个变量矩阵,返回NDIS_STATUS)
NDIS_STATUS setOID_XXX_YYY (T* pValue,IN OUT PULONG ByteCount, OUT PULONG ByteNeeded);
为了决定选择哪一种特定OID处理程序,DriverNetworks开发一种C++技术基于OID属性为给定的OID来选择一个处理程序原型。OID属性参考OID Traits。DriverNetworks基于对既定OID的常规处理为大多数NDIS OIDs定义了OID Traits。例如,大多数NDIS miniport驱动都对OID_GEN_MAXIMUM_FRAME_SIZE查询响应并返回某些常数值。查询的第一种形式是OID消息映射激活的默认选择。然而,C++ traits技术允许忽略很多细节。(参看OID Traits)。
DriverNetworks并不限制处理程序怎样实现的,只要它们的特征和上面的一种形式匹配就可以了。例如,short property处理程序(查询和设置的第一种形式)一般都是内联的或者静态的,和DDK的实现具有一样的运行效率。
3.OID Traits
OID Traits声明了NDIS OIDs的属性,也就定义了OID消息映射使用的OID 处理程序的形式。The OID traits可以是可变的和也可以是不可变的。
不可变的Traits声明了OID的本质属性,例如OID代表的值得长度。不可变traits从DDK的定义中派生而来的(参考表1.1 Network Drivers: Windows 2000)。在DriverNetworks中,不可变traits由KNdisOid模板类描述,并且通过KNdisOidTraitsBase模板类直接交给用户。这些模板的参数都是OID_XXX_YYY常数值。DriverNetworks定义了下面的不可变traits:
1) 类型:代表给定OID的值的类型。
2) ULONG 长度:值的字节长度 (通常有, sizeof(Type))
3) bool IsVarSize():说明OID是描述一个类型Type的一个单元还是一个变量大小的矩阵。
可变Traits声明了用户可修改的OID的属性。可变OID traits通过KNdisOidTraits模板类直接提供给用户。这个模板由KNdisOidTraitsBase类派生的,并且参数包括OID_XXX_YYY常量值和adapter类的类型。C++ 模板规范经过特殊处理后提供可变性。DriverNetworks定义了下面一些可变trait:
•bool Failable():说明在给定的miniport驱动中OID查询请求曾经是否失败过。当一个OID查询返回的不是NDIS_STATUS_SUCCESS,就认为它是失败了。
Failable()仅用于查询请求。所有的设置请求都认为是失败的。所有的查询请求默认为非失败的。用户通过为特殊的adapter类型定义Failable(),而不用考虑细节。例如:
inline bool
KNdisOidTraits::Failable() { return true; }
这样一来,对于给定的adapter――MyAdapter,查询链接速率可能会是会失败。因此,OID解码的MyAdapter::getOID_GEN_LINK_SPEED()处理程序运用查询的第二种形式,而不是第一种形式。
OID处理程序的形式根据IsVarSize和Failable traits来决定。下表总结了DriverNetworks基于OID traits选择正确的处理程序的形式。
Request IsVarSize Failable Handler Form Comment/Example
Query F F q1 Trivial const parameter
Query F T q2 Run time read value
Query T F q3 Input size might not be valid
Query T T q3 Data array read from hardware
Set F F N/A Set() can fail by definition
Set T F N/A Set() can fail by definition
Set F T s1 Set a single parameter value
Set T T s2 Set an array of values
4.添加和删除OID支持
1)为专门的OID_XXX_YYY添加支持
下面应该对项目文件XxxOids.cpp进行修改,并假设NDIS Miniport驱动项目由Network Driver Wizard生成的。
1. 在需要支持的OIDs矩阵中添加OID_XXX_YYY 。也就是将OID_XXX_YYY符号插入到静态矩阵sm_OID_GEN_SUPPORTED_LIST[]中。注意:该矩阵OIDs的上升顺序不能被打乱。
2. 如果要实现OID_XXX_YYY 的查询,在OID查询消息映射中插入一行OID_QUERY_ENTRY(OID_XXX_YYY)。如果已经实现了一组OID_XXX_YYY,就将OID_SET_ENTRY(OID_XXX_YYY)插入到OID查询消息映射中。
3. 如果查询请求可能会失败或者是异步完成的,就可以不考虑Failable() trait 。这是可选的。
4. 实现查询,如果可应用,那么基于上表描述的原型设置处理程序。
2)为专门的OID_XXX_YYY删除支持
假设NDIS Miniport驱动项目由Network Driver Wizard生成的。
1. 注释sm_OID_GEN_SUPPORTED_LIST[]矩阵的OID_XXX_YYY;
2. 从OID消息映射中注释OID_QUERY/SET_ENTRY。
注意: 可以选择删除处理程序代码,因为这些代码将被优化掉。
DriverNetwork miniport驱动学习(9)
KNdisLookahead类
:
KNdisLookahead抽象了完成部分分组的NDIS机制。Miniport驱动允许协议先检查接收帧的头部和一小部分内容。而不需要立即将整个帧都交给它。因此,协议要先调用介质相关NdisMXXXReceiveIndicate() API。协议然后调用MiniportTransferData处理程序来接收分组剩余内容。
模板类KNdisLookahead封装了部分说明机制。该模板由两个参数:
L :最大Lookahead缓冲长度;缓冲创建在类内。
M:介质类型;定义了怎样调用NDIS来说明帧;802.3是默认的。
类的使用
1. 在adapter 类中包括一个成员数据KNdisLookahead<> 。例如:
KNdisLookahead<256> m_Lookahead;
2. 在adapter 类中, 提供下面的公共函数:
UINT CopyDataUp(PVOID FrameBase, PUCHAR Buf, UINT Len, UINT Offset);
这同步传输的内容有:长度Len,起始偏移Offset,缓冲Buf中当前帧的内容。The frame is identified by an opaque to the framework value FrameBase. 驱动开发者用硬件特定的方法使用FrameBase。也就是,它可能说明了卡上环形缓冲中当前帧的偏移地址。
3. 在HandleInterrupt()处理程序中,在配置用来说明的接收数据长度和卡内存中的帧的FrameBase 之后,调用:
m_Lookahead.Indicate (this, header_len, lookah_len, packet_len, FrameBase);
这样帧的lookahead部分将存入KNdisLookahead实体中,并且接着调用NDIS来说明lookahead。因此NDIS就可以将lookahead展示给捆绑的协议。 如果协议对该分组感兴趣,控制权将转到适配的TransferData()处理程序。参看第五步。FrameBase值存在类中并且当调用CopyDataUp()时就回传给CopyDataUp()。 如果在硬件中存在多个接收帧,上面的过程就重复多次。
4. 在离开HandleInterrupt()处理程序之前,至少要调用下面一次:
m_Lookahead.IndicateComplete(this);
这样调用NDIS说明当前接收突发结束。
5. 在TransferData()处理程序中调用:
m_Lookahead.Transfer(this, Packet, BytesToCopy, ByteOffset);
这将传输当前帧的部分给协议提供的分组。该当前帧的部分是协议请求的且偏移量为ByteOffset。
注意:在 m_Lookahead.Indicate()和Adapter::TransferData()调用之前,连续 miniports不能被调用(除了中断)。 完整交互过程都是DISPATCH_LEVEL ,卡上的中断去能了。当调用TransferData(),当前帧部分存在m_LookaheadData,部分仍然在卡上。KNdisLookahead::Transfer()知道从哪里传输数据。如果协议对该帧不敢兴趣,TransferData()处理程序就根本不调用。Lookahead缓冲将给下一个接收帧覆盖。
Members
KNdisLookahead
DrvierNetwork miniport驱动学习(10)
前言
这一次重新研读 DriverNetwork 的有关 miniport 驱动的帮助文档,并以 DriverNetwork 中自带的 nmne2k 例子说明相应的知识点。
第一次重新学习内容: NDIS 的 miniport 驱动框架
NDIS miniport 的 adapter 类继承了 KNdisMiniAdapter 类,主要声明了所需的例程,例如 Initialize(), Halt() 和 Reset() 。这些例程可以是虚拟的或者非虚拟的,或者是派生类内联的成员函数。这些例程和模块的命名和 DDK 一一对应: Initialize() 对应 MiniportInitialize() ; Halt() 对应 MiniportHalt() 。对这些例程的处理和 IRQL 级也和 DDK 相应的 MiniportXxxx() 一样。
参看文档 Understanding the NDIS Miniport Driver Framework 的图:该图描述了 DriverNetworks 框架类和驱动开发者要实现的类(黄颜色给出了)之间的关系。
驱动开发者提供了两个主要的类:
1 ) KNdisMiniDriver 派生 driver 类: class NMNE2KDriver : protected KNdisMiniDrive
driver 派生类负责向 NDIS 注册 miniport 的 adapter ,并且必须实现唯一的虚拟方法 KNdisMiniDriver::DriverEntry() 。 DriverEntry() 一般只要注册 KNdisMiniAdapter 派生的 miniport 的 adapter 类。在向 NDIS 注册了 adapter 之后, NDIS 立即调用 adapter 类的 Initialize() 例程。 DriverNetwork 框架要求源文件包括宏: DECLARE_MINIDRIVER_CLASS(DriverClass)
2 ) KNdisMiniAdapter 派生 adapter 类: class NMNE2KAdapter : public KNdisMiniAdapter
Adapter 类实现了全部的方法,分成两种类型: handlers 和 service 。 Handlers :驱动提供给 NDIS 回调用的,例如 Initialize() , Halt() 和 Reset() 等等。 NDIS 通过 KNdisWrapper 类调用这些 handlers 。对于每一个适配器类 A ,框架创建了一个 KNdisWrapper 的一个实现,将 NDIS 回调转发给 A 定义的正确的 handlers 。所需要的 handlers 都在 Characteristics.h 文件的宏 KNDIS_MINIPORT_HANDLER 声明了,而且都必须声明,如果在该头文件中声明而又没有在 adapter 类中实现, NDIS 就会调用默认的基类 handler ,报告 “Handler not implemented!Check Characteristics.h” 。 services 是 NDIS 提供给 miniport 调用的方法,这些服务通常由 handler 调用。
Instantiation 过程: DriverNetwork 框架自动生成一个 KNdisMniportAdapter 派生类的实体。框架向 NDIS 注册,截取初始 NDIS 的 MiniportInitialize() 回调函数,并创建一个 adapter 类的实体。创建 adapter 实体之后,框架将控制权交给 Initialize() handler , Initialize() 完成如下功能: 1 )声明硬件资源 2 )对 NIC 初始化。从 Initialize() 成功返回之后系统才能使用 adapter 。
Destruction 过程: KNdisMiniportAdapter 派生对象的销毁也由框架实现。在销毁之前,调用 Halt() handler ,完成如下功能: 1 )执行硬件关闭 2 )释放在 Initialize() 声明的资源。
成员函数:
KNdisMiniAdapter - 构造函数
~KNdisMiniAdapter - 析构函数
AllocateComplete - 共享内存分配完成 (handler)
CancelSendPackets - 取消悬挂状态的发送分组 (handler)(NDIS5.1 only)
当捆绑的协议或者中间驱动执行 NdisCancelSendPackets 请求时,调用该 handle 。如果 miniport 将分组在内部排队,他应该遍历整个悬挂发送分组链表,并且取消每一个取消 id 和 CancelId 匹配的分组。
CheckForHang - 检测适配器的悬挂条件 (handler)
这是一个可选 handler 。如果定义了该 handler , NDIS 每两秒钟调用一次。如果 CheckForHang 返回真, NDIS 就调用驱动的 Reset() handler 。如果满足悬挂条件,就返回真。
DisableInterrupt - 适配器中断去能 (handler)
EnableInterrupt - 适配器中断使能 (handler)
GetDeviceProperty