目录
2 WDF_NO_EVENT_CALLBACK初始化驱动标志
3 设置WdfDriverInitNoDispatchOverride表示框架不能拦截IO直接发给驱动的Irps
7 注册小端口 其中因为要保护全局变量 所以需要有一个锁 另外需要有一个链表储存需要的信息 所以要初始化它们
11 最后 MPInitialize的实现以及MPHalt的实现
1 首先进入DriverEntry检查版本号(可选)
// 检查版本号。这里主版本号为5,次版本号为0,要求最低版本为5.0
if (NdisGetVersion() < ((MP_NDIS_MAJOR_VERSION << 16) | MP_NDIS_MINOR_VERSION)
{
DEBUGP(MP_ERROR, ("This version of driver is not support on this OS\n"));
// 如果版本太低,则直接返回失败即可。
return NDIS_STATUS_FAILURE;
}
2 WDF_NO_EVENT_CALLBACK初始化驱动标志
WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);
3 设置WdfDriverInitNoDispatchOverride表示框架不能拦截IO直接发给驱动的Irps
config.DriverInitFlags |= WdfDriverInitNoDispatchOverride;
4 创建WDFDriver对象
ntStatus = WdfDriverCreate(DriverObject,
RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES,
&config,
WDF_NO_HANDLE);
if (!NT_SUCCESS(ntStatus)){
DEBUGP(MP_ERROR, ("WdfDriverCreate failed\n"));
return NDIS_STATUS_FAILURE;
}
5 初始化一个包装句柄(Wrapper Handler)
NDIS使用这个包装句柄来管理小端口的配置信息,但是我们不需要调用这个句柄获取任何信息,只是需要持有它来管理API调用
// 初始化包装句柄。这个句柄是注册小端口必须的。但是对小端口
// 驱动的开发者而言,除了调用一些NDIS函数需要提供这个句柄之
// 外,并没有什么实质的意义。
NdisMInitializeWrapper(
&NdisWrapperHandle,
DriverObject,
RegistryPath,
NULL
);
if (!NdisWrapperHandle){
DEBUGP(MP_ERROR, ("NdisMInitializeWrapper failed\n"));
return NDIS_STATUS_FAILURE;
}
6 填写小端口特征
// 然后开始填写小端口特征。
MPChar.MajorNdisVersion = MP_NDIS_MAJOR_VERSION;
MPChar.MinorNdisVersion = MP_NDIS_MINOR_VERSION;
MPChar.InitializeHandler = MPInitialize;
MPChar.HaltHandler = MPHalt;
MPChar.SetInformationHandler = MPSetInformation;
MPChar.QueryInformationHandler = MPQueryInformation;
MPChar.SendPacketsHandler = MPSendPackets;
MPChar.ReturnPacketHandler = MPReturnPacket;
MPChar.ResetHandler = NULL;//MPReset;
MPChar.CheckForHangHandler = MPCheckForHang; //optional
#ifdef NDIS51_MINIPORT
MPChar.CancelSendPacketsHandler = MPCancelSendPackets;
MPChar.PnPEventNotifyHandler = MPPnPEventNotify;
MPChar.AdapterShutdownHandler = MPShutdown;
#endif
DEBUGP(MP_LOUD, ("Calling NdisMRegisterMiniport...\n"));
7 注册小端口 其中因为要保护全局变量 所以需要有一个锁 另外需要有一个链表储存需要的信息 所以要初始化它们
另外为了释放这些全局资源,防止内存泄露 还需要注册一个Unload函数在驱动卸载的时候调用
// 注册小端口。注意需要包装句柄与小端口特征。
Status = NdisMRegisterMiniport(
NdisWrapperHandle,
&MPChar,
sizeof(NDIS_MINIPORT_CHARACTERISTICS));
if (Status != NDIS_STATUS_SUCCESS) {
DEBUGP(MP_ERROR, ("Status = 0x%08x\n", Status));
NdisTerminateWrapper(NdisWrapperHandle, NULL);
} else {
// 初始化全局变量。这些全局变量是在整个驱动中使用的
NdisAllocateSpinLock(&GlobalData.Lock);
NdisInitializeListHead(&GlobalData.AdapterList);
// 注册一个Unload函数。请注意Unload是整个驱动卸载的时候调用。
// 而协议特征中的MPHalt则是每个实例(网卡)卸载的时候调用的。
NdisMRegisterUnloadHandler(NdisWrapperHandle, MPUnload);
}
到这里 小端口DriverEntry就结束了,逻辑很简单 和协议驱动完全一致
下面就是DriverEntry中提到的链表和锁结构介绍
8 小端口驱动的Adapter结构
一个小端口驱动对应一个网卡(物理或者虚拟均可) 这个结构包含了我们需要的所有信息,所以部分回调函数会使用这个结构作为形参,
而且一台电脑上可能会有多块网卡(物理或虚拟的),小端口需要为每个网卡设置一个Adapter结构保存相关信息,这就是为什么需要链表(存储信息)以及锁(同步)的原因
结构太长可以不看
我们只需要知道这个结构包含;链表结点、引用计数、锁、事件、物理设备和功能设备、缓冲池、句柄、适配器名字等信息
typedef struct _MP_ADAPTER
{
LIST_ENTRY List;
LONG RefCount;
NDIS_SPIN_LOCK Lock;
NDIS_EVENT InitEvent;
NDIS_EVENT HaltEvent;
//
// Keep track of various device objects.
//
PDEVICE_OBJECT Pdo;
PDEVICE_OBJECT Fdo;
PDEVICE_OBJECT NextDeviceObject;
WDFDEVICE WdfDevice;
NDIS_HANDLE AdapterHandle;
WCHAR AdapterName[NIC_ADAPTER_NAME_SIZE];
WCHAR AdapterDesc[NIC_ADAPTER_NAME_SIZE];
ULONG Flags;
UCHAR PermanentAddress[ETH_LENGTH_OF_ADDRESS];
UCHAR CurrentAddress[ETH_LENGTH_OF_ADDRESS];
WDFWORKITEM WorkItemForNdisRequest;
BOOLEAN Promiscuous;
BOOLEAN RequestPending;
BOOLEAN ResetPending;
BOOLEAN IsHardwareDevice;
BOOLEAN IsTargetSupportChainedMdls;
//
// Variables to track resources for the send operation
//
NDIS_HANDLE SendBufferPoolHandle;
LIST_ENTRY SendFreeList;
LIST_ENTRY SendWaitList;
LIST_ENTRY SendBusyList;
PUCHAR TCBMem;
LONG nBusySend;
NDIS_SPIN_LOCK SendLock;
//
// Variables to track resources for the Receive operation
//
LIST_ENTRY RecvFreeList;
LIST_ENTRY RecvBusyList;
NDIS_SPIN_LOCK RecvLock;
LONG nBusyRecv;
NDIS_HANDLE RecvPacketPoolHandle;
NDIS_HANDLE RecvBufferPoolHandle;
PUCHAR RCBMem;
WDFWORKITEM ReadWorkItem;
WDFWORKITEM ExecutiveCallbackWorkItem;
LONG IsReadWorkItemQueued;
//
// Packet Filter and look ahead size.
//
ULONG PacketFilter;
ULONG ulLookAhead;
ULONG ulLinkSpeed;
// multicast list
ULONG ulMCListSize;
UCHAR MCList[NIC_MAX_MCAST_LIST][ETH_LENGTH_OF_ADDRESS];
// Packet counts
ULONG64 GoodTransmits;
ULONG64 GoodReceives;
ULONG NumTxSinceLastAdjust;
// Count of transmit errors
ULONG TxAbortExcessCollisions;
ULONG TxLateCollisions;
ULONG TxDmaUnderrun;
ULONG TxLostCRS;
ULONG TxOKButDeferred;
ULONG OneRetry;
ULONG MoreThanOneRetry;
ULONG TotalRetries;
ULONG TransmitFailuresOther;
// Count of receive errors
ULONG RcvCrcErrors;
ULONG RcvAlignmentErrors;
ULONG RcvResourceErrors;
ULONG RcvDmaOverrunErrors;
ULONG RcvCdtFrames;
ULONG RcvRuntErrors;
//
// Talking to NDISPROT protocol
//
HANDLE FileHandle;
PFILE_OBJECT FileObject;
WDFIOTARGET IoTarget;
UCHAR PhyNicMacAddress[ETH_LENGTH_OF_ADDRESS];
PCALLBACK_OBJECT CallbackObject;
PVOID CallbackRegisterationHandle;
WDFREQUEST StatusIndicationRequest;
NDISPROT_INDICATE_STATUS NdisProtIndicateStatus;
WDFMEMORY WdfStatusIndicationBuffer;
//
// Statistic for debugging & validation purposes
//
ULONG nReadsPosted;
ULONG nReadsCompletedWithError;
ULONG nPacketsIndicated;
ULONG nPacketsReturned;
ULONG nBytesRead;
ULONG nPacketsArrived;
ULONG nWritesPosted;
ULONG nWritesCompletedWithError;
ULONG nBytesArrived;
ULONG nBytesWritten;
ULONG nReadWorkItemScheduled;
} MP_ADAPTER, *PMP_ADAPTER;
9 配置信息的读取
配置信息读取是在NICReadRegParameters中实现的,这个函数又是在MPInitialize中被调用。
而实现的主要步骤是利用NdisOpneConfiguration函数打开包装句柄,然后利用NdisReadConfiguration函数读取信息
NDIS_STATUS
NICReadRegParameters(
PMP_ADAPTER Adapter,
NDIS_HANDLE WrapperConfigurationContext)
/*++
Routine Description:
Read device configuration parameters from the registry
Arguments:
Adapter Pointer to our adapter
WrapperConfigurationContext For use by NdisOpenConfiguration
Should be called at IRQL = PASSIVE_LEVEL.
Return Value:
NDIS_STATUS_SUCCESS
NDIS_STATUS_FAILURE
NDIS_STATUS_RESOURCES
--*/
{
NDIS_STATUS Status = NDIS_STATUS_SUCCESS;
NDIS_HANDLE ConfigurationHandle;
PUCHAR NetworkAddress;
UINT Length;
PUCHAR pAddr;
PNDIS_CONFIGURATION_PARAMETER pParameterValue;
NDIS_STRING strMiniportName = NDIS_STRING_CONST("MiniportName");
NDIS_STRING strFilterName = NDIS_STRING_CONST("Promiscuous");
DEBUGP(MP_TRACE, ("--> NICReadRegParameters\n"));
PAGED_CODE();
//
// Open the registry for this adapter to read advanced
// configuration parameters stored by the INF file.
//
NdisOpenConfiguration(
&Status,
&ConfigurationHandle,
WrapperConfigurationContext);
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(MP_ERROR, ("NdisOpenConfiguration failed\n"));
return NDIS_STATUS_FAILURE;
}
do
{
//
// Read the Miniport Name.
// This feature is available only XP and above.
//
NdisReadConfiguration(&Status,
&pParameterValue,
ConfigurationHandle,
&strMiniportName,
NdisParameterString);
if (Status != NDIS_STATUS_SUCCESS) {
DEBUGP(MP_ERROR, ("NdisReadConfiguration for miniport name failed\n"));
} else {
Length = min(NIC_ADAPTER_NAME_SIZE-1,
pParameterValue->ParameterData.StringData.Length/sizeof(WCHAR));
RtlStringCchCopyW(Adapter->AdapterName, Length+1,
(PWCHAR)pParameterValue->ParameterData.StringData.Buffer);
}
//
// Read the Promiscuous filter value.
//
NdisReadConfiguration(&Status,
&pParameterValue,
ConfigurationHandle,
&strFilterName,
NdisParameterInteger);
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(MP_ERROR, ("NdisReadConfiguration for promiscuous key failed\n"));
break;
}
Adapter->Promiscuous = (BOOLEAN)pParameterValue->ParameterData.IntegerData;
} WHILE (FALSE);
10 设置适配器上下文(adapter context)
适配器上下文是一个指针,这个指针指向的空间就是适配器结构,这个指针必须在MPInitialize中设置好,然后在多个小端口特征中的回调函数中作为实参传入,而设置适配器上下文是通过NdisMSetAttributesEx这个NDIS内核API函数做到的
这个函数原型如下:
VOID NdisMSetAttributesEx(
_In_ NDIS_HANDLE MiniportAdapterHandle,
_In_ NDIS_HANDLE MiniportAdapterContext,
_In_opt_ UINT CheckForHangTimeInSeconds,
_In_ ULONG AttributeFlags,
_In_opt_ NDIS_INTERFACE_TYPE AdapterType
);
对于形参的解释请参考MSDN
11 最后 MPInitialize的实现以及MPHalt的实现
MPInitialize就是对应小端口特征中的InitializeHandler特征的回调函数
这个函数在发现每个实例(网卡)时被Windows内核调用,因此我们应该在这个函数中实现对adapter结构的初始化
另外需要在这个回调中生成一个小端口设备对象
然后就是MPHalt,这个回调负责网卡被拔出或者停止工作时负责释放所有资源,在MPHalt中有一个MPShutdown函数,在MPSHoutdown中完成关闭网卡硬件等操作:
1 释放并解除所有映射过的IO端口的映射
2 取消注册的所有中断