NDIS小端口驱动
1.概述
NDIS小端口驱动又称NIC驱动,即真正的网卡驱动,其绑定于网卡,包括物理网卡与虚拟网卡,在NDIS层次中处于最底层。上层是NDIS协议驱动或者是中间层驱动。和协议驱动一样,小端口驱动也提供了一套特征接口,主要用于发送数据包,以及获取或者设置一些网卡信息。
2.代码实现
2.1 DriverEntry函数
和wdm驱动一样,以wdf实现的NDIS小端口驱动同样具有着DriverEntry驱动入口函数,在入口函数中主要注册了一套自己实现的特征函数。
NTSTATUS
DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
In the context of its DriverEntry function, a miniport driver associates
itself with NDIS, specifies the NDIS version that it is using, and
registers its entry points.
Arguments:
DriverObject - pointer to the driver object.
RegistryPath - pointer to the driver registry path.
Return Value:
NDIS_STATUS_xxx code
--*/
{
NDIS_STATUS Status;
NDIS_MINIPORT_CHARACTERISTICS MPChar;
WDF_DRIVER_CONFIG config;
NTSTATUS ntStatus;
DEBUGP(MP_INFO, ("---> NDISWDF sample built on "__DATE__" at "__TIME__ "\n"));
DEBUGP(MP_INFO, ("---> Sample is built with NDIS Version %d.%d\n",
MP_NDIS_MAJOR_VERSION, MP_NDIS_MINOR_VERSION));
// 检查版本号。这里主版本号为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;
}
WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);
//
// Set WdfDriverInitNoDispatchOverride flag to tell the framework
// not to provide dispatch routines for the driver. In other words,
// the framework must not intercept IRPs that the I/O manager has
// directed to the driver. In this case, it will be handled by NDIS
// port driver.
//
config.DriverInitFlags |= WdfDriverInitNoDispatchOverride;
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;
}
// 初始化包装句柄。这个句柄是注册小端口必须的。但是对小端口
// 驱动的开发者而言,除了调用一些NDIS函数需要提供这个句柄之
// 外,并没有什么实质的意义。
NdisMInitializeWrapper(
&NdisWrapperHandle,
DriverObject,
RegistryPath,
NULL
);
if (!NdisWrapperHandle) {
DEBUGP(MP_ERROR, ("NdisMInitializeWrapper failed\n"));
return NDIS_STATUS_FAILURE;
}
// 把小端口特征清0。
NdisZeroMemory(&MPChar, sizeof(MPChar));
// 然后开始填写小端口特征。
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"));
// 注册小端口。注意需要包装句柄与小端口特征。
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);
}
DEBUGP(MP_TRACE, ("<--- DriverEntry\n"));
return Status;
DriverEntry所做的事情主要如下:
1.首先生成一个wdf驱动对象,虽然没有对象输出。
2.NdisMInitializeWrapper初始化包装句柄,后面可用于注册小端口
3.填写小端口特征
4.NdisMRegisterMiniport注册小端口
5.NdisMRegisterUnloadHandler注册一个卸载函数,此函数是在驱动卸载时调用。
2.2 MPInitialize
NDIS_STATUS
MPInitialize(
OUT PNDIS_STATUS OpenErrorStatus,
OUT PUINT SelectedMediumIndex,
IN PNDIS_MEDIUM MediumArray,
IN UINT MediumArraySize,
IN NDIS_HANDLE MiniportAdapterHandle,
IN NDIS_HANDLE WrapperConfigurationContext
)
/*++
Routine Description:
The MiniportInitialize function is a required function that sets up a
NIC (or virtual NIC) for network I/O operations, claims all hardware
resources necessary to the NIC in the registry, and allocates resources
the driver needs to carry out network I/O operations.
MiniportInitialize runs at IRQL = PASSIVE_LEVEL.
Arguments:
Return Value:
NDIS_STATUS_xxx code
--*/
{
NDIS_STATUS Status = NDIS_STATUS_SUCCESS;
PMP_ADAPTER Adapter = NULL;
UINT index;
NTSTATUS ntStatus;
ULONG nameLength;
WDF_OBJECT_ATTRIBUTES doa;
UNREFERENCED_PARAMETER(OpenErrorStatus);
DEBUGP(MP_TRACE, ("---> MPInitialize\n"));
PAGED_CODE();
do {
// 检查可选的媒质类型中有没有NdisMedium802_3.
for (index = 0; index < MediumArraySize; ++index)
{
if (MediumArray[index] == NIC_MEDIA_TYPE) {
break;
}
}
// 如果没有当然就直接返回不支持了。
if (index == MediumArraySize)
{
DEBUGP(MP_ERROR, ("Expected media is not in MediumArray.\n"));
Status = NDIS_STATUS_UNSUPPORTED_MEDIA;
break;
}
// 填写选中的媒质。
*SelectedMediumIndex = index;
// 下面是分配初始化适配器结构。非常简单,不详述。
Status = NICAllocAdapter(&Adapter);
if (Status != NDIS_STATUS_SUCCESS)
{
break;
}
// 增加引用计数。这个引用计数对应的在MPHalt中减少。
MP_INC_REF(Adapter);
//
// NdisMGetDeviceProperty function enables us to get the:
// PDO - created by the bus driver to represent our device.
// FDO - created by NDIS to represent our miniport as a function
// driver.
// NextDeviceObject - deviceobject of another driver (filter)
// attached to us at the bottom.
// Since our driver is talking to NDISPROT, the NextDeviceObject
// is not useful. But if we were to talk to a driver that we
// are attached to as part of the devicestack then NextDeviceObject
// would be our target DeviceObject for sending read/write Requests.
//
NdisMGetDeviceProperty(MiniportAdapterHandle,
&Adapter->Pdo,
&Adapter->Fdo,
&Adapter->NextDeviceObject,
NULL,
NULL);
//请求设备相关信息,这里是用来获取描述设备的字符串
ntStatus = IoGetDeviceProperty(Adapter->Pdo,
DevicePropertyDeviceDescription,
NIC_ADAPTER_NAME_SIZE,
Adapter->AdapterDesc,
&nameLength);
if (!NT_SUCCESS(ntStatus))
{
DEBUGP(MP_ERROR, ("IoGetDeviceProperty failed (0x%x)\n",
ntStatus));
Status = NDIS_STATUS_FAILURE;
break;
}
Adapter->AdapterHandle = MiniportAdapterHandle;
MP_SET_FLAG(Adapter, fMP_INIT_IN_PROGRESS);
//
// Read Advanced configuration information from the registry
//
//从注册表中读取信息放置在Adapter中
//主要读取了网络地址,mac地址,混杂过滤器值和miniport名称.
//并将以上读取的值写入到Adapter中
Status = NICReadRegParameters(Adapter, WrapperConfigurationContext);
if (Status != NDIS_STATUS_SUCCESS)
{
break;
}
//
// Inform NDIS about significant features of the NIC. A
// MiniportInitialize function must call NdisMSetAttributesEx
// (or NdisMSetAttributes) before calling any other NdisMRegisterXxx
// or NdisXxx function that claims hardware resources. If your
// hardware supports busmaster DMA, you must specify
// NDIS_ATTRIBUTE_BUS_MASTER.
// If this is NDIS51 miniport, it should use safe APIs. But if this
// is NDIS 5.0, NDIS might assume that we are not using safe APIs
// and try to map every send buffer MDLs. As a result, let us tell
// NDIS, even though we are NDIS 5.0, we use safe buffer APIs by
// setting the flag NDIS_ATTRIBUTE_USES_SAFE_BUFFER_APIS
//设置适配器上下文
NdisMSetAttributesEx(
MiniportAdapterHandle,
(NDIS_HANDLE)Adapter,
0,
#ifdef NDIS50_MINIPORT
NDIS_ATTRIBUTE_DESERIALIZE |
NDIS_ATTRIBUTE_USES_SAFE_BUFFER_APIS,
#else
NDIS_ATTRIBUTE_DESERIALIZE,
#endif
NIC_INTERFACE_TYPE);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&doa, WDF_DEVICE_INFO);
//生成小端口设备对象
ntStatus = WdfDeviceMiniportCreate(WdfGetDriver(),
&doa,
Adapter->Fdo,
Adapter->NextDeviceObject,
Adapter->Pdo,
&Adapter->WdfDevice);//小端口设备对象
if (!NT_SUCCESS(ntStatus))
{
DEBUGP(MP_ERROR, ("WdfDeviceMiniportCreate failed (0x%x)\n",
ntStatus));
Status = NDIS_STATUS_FAILURE;
break;
}
GetWdfDeviceInfo(Adapter->WdfDevice)->Adapter = Adapter;
//
// Get the Adapter Resources & Initialize the hardware.
//
//启动网卡
Status = NICInitializeAdapter(Adapter, WrapperConfigurationContext);
if (Status != NDIS_STATUS_SUCCESS) {
Status = NDIS_STATUS_FAILURE;
break;
}
} WHILE(FALSE);
if (Status == NDIS_STATUS_SUCCESS) {
//
// Attach this Adapter to the global list of adapters managed by
// this driver.
//将Adapter指针保存在全局链表中
NICAttachAdapter(Adapter);
}
else {
if (Adapter) {
//释放资源
MPHalt(Adapter);
}
}
DEBUGP(MP_TRACE, ("<--- MPInitialize Status = 0x%08x%\n", Status));
return Status;
}
在这里涉及到了适配器上下文的概念,适配器上下文是指向适配器结构的指针,而适配器结构里面主要成员大都是关于收发数据包相关的一些资源,在之后收发数据包的特征函数中,只有知道了适配器上下文才会知道通过哪个网卡进行收发数据包。不管是协议驱动中的打开上下文,还是小端口驱动中的适配器,都是通过我们自己申请内存来实现的
这里的MPInitialize函数做的都是收发数据包之前的一些准备工作,如下:
1.分配初始化适配器结构,其内部申请空间,并初始化了适配器结构中的锁Lock,链表结点List,用于同步的事件
2.NdisMGetDeviceProperty获取小端口驱动通信所需的设备对象
3.NICReadRegParameters从注册表中网络地址,mac地址,混杂过滤器值和miniport名称并存储在适配器结构中
4.NdisMSetAttributesEx设置适配器上下文
5.WdfDeviceMiniportCreate生成小端口设备对象
6.NICInitializeAdapter启动网卡
7.NICAttachAdapter将适配器指针保存在全局链表中
其中第八步NICInitializeAdapter中还有着更为重要的操作,其内部主要创建了一个工作任务,以及调用NICInitAdapterWorker。
NTSTATUS
NICInitAdapterWorker(
PMP_ADAPTER Adapter
)
/*++
Routine Description:
This is the worker routine for doing all the initialization work.
It opens the target device, allocates send & receive side resources
and spawns a worker thread to start receiving packets from the
target device.
Arguments:
Adapter Pointer to our adapter
Return Value:
NT Status code
--*/
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
NDIS_STATUS Status;
ULONG unUsed;
WDF_OBJECT_ATTRIBUTES attributes;
DEBUGP(MP_WARNING, ("--> NICInitAdapterWorker %p\n", Adapter));
PAGED_CODE();
//
// Let us make sure only one thread is executing this routine by
// waiting on this synchronization event.
//
DEBUGP(MP_WARNING, ("Waiting on the InitEvent...\n"));
NdisWaitEvent(&Adapter->InitEvent, 0);
do {
if (!MP_TEST_FLAG(Adapter, fMP_INIT_IN_PROGRESS)) {
//
// Adapter has been initialized successfully.
//
ntStatus = STATUS_SUCCESS;
break;
}
#ifdef INTERFACE_WITH_NDISPROT
// 打开协议驱动的接口。
ntStatus = NICOpenNdisProtocolInterface(Adapter);
if (!NT_SUCCESS(ntStatus)) {
break;
}
Adapter->IsTargetSupportChainedMdls = FALSE;
#else
//
// To retrieve a handle to a device's local I/O target, the driver calls WdfDeviceGetIoTarget.
//返回本地IO目标
Adapter->IoTarget = WdfDeviceGetIoTarget(Adapter->WdfDevice);
if (Adapter->IoTarget == NULL) {
DEBUGP(MP_ERROR, ("WdfDeviceGetIoTarget failed \n"));
break;
}
#endif
Status = NICAllocSendResources(Adapter);
if (Status != NDIS_STATUS_SUCCESS)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
DEBUGP(MP_ERROR, ("Failed to allocate send side resources\n"));
break;
}
//生成读包工作任务,并把这个工作任务保存在Adapter->ReadWordItem中
Status = NICAllocRecvResources(Adapter);
if (Status != NDIS_STATUS_SUCCESS)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
DEBUGP(MP_ERROR, ("Failed to send side resources\n"));
break;
}
ntStatus = NICSendRequest(Adapter,
NdisRequestQueryInformation,
OID_GEN_LINK_SPEED,
&Adapter->ulLinkSpeed,
sizeof(ULONG),
&unUsed,
&unUsed
);
if (!NT_SUCCESS(ntStatus)){
DEBUGP(MP_ERROR, ("-->Unable to get link speed %x\n", ntStatus));
break;
}
//
// If the device property is set to promiscuous, we will set the
// target NIC to promiscuous mode and use a locally administered
// MAC address. Otherwise, we will assume the MAC address of the
// target NIC (We are assuming that user must have disabled the
// binding of TCP/IP with the target NIC).
//
if (Adapter->Promiscuous) {
ULONG PacketFilter = NDIS_PACKET_TYPE_PROMISCUOUS;
DEBUGP(MP_TRACE, ("Set the physical NIC to promiscuous mode\n"));
ntStatus = NICSendRequest(Adapter,
NdisRequestSetInformation,
OID_GEN_CURRENT_PACKET_FILTER,
&PacketFilter,
sizeof(ULONG),
&unUsed,
&unUsed
);
if (!NT_SUCCESS(ntStatus)){
DEBUGP(MP_ERROR, ("-->Unable to set the NIC to promiscuous mode %x\n", ntStatus));
break;
}
} else {
//
// Then get the physical NICs MAC address and use that as our
// MAC address. Hoping that user must have disabled TCP/IP
// bindings with the physcial NIC.
//
ntStatus = NICSendRequest(Adapter,
NdisRequestQueryInformation,
OID_802_3_CURRENT_ADDRESS,
Adapter->PhyNicMacAddress,
ETH_LENGTH_OF_ADDRESS,
&unUsed,
&unUsed
);
if (!NT_SUCCESS(ntStatus)){
DEBUGP(MP_ERROR, ("-->Unable to get mac address %x\n", ntStatus));
break;
}
ETH_COPY_NETWORK_ADDRESS(Adapter->CurrentAddress,
Adapter->PhyNicMacAddress);
DEBUGP(MP_WARNING, ("Current Address = %02x-%02x-%02x-%02x-%02x-%02x\n",
Adapter->CurrentAddress[0],
Adapter->CurrentAddress[1],
Adapter->CurrentAddress[2],
Adapter->CurrentAddress[3],
Adapter->CurrentAddress[4],
Adapter->CurrentAddress[5]));
}
MP_CLEAR_FLAG(Adapter, fMP_INIT_IN_PROGRESS);
MP_CLEAR_FLAG(Adapter, fMP_DISCONNECTED);
MP_SET_FLAG(Adapter, fMP_POST_WRITES);
MP_SET_FLAG(Adapter, fMP_POST_READS);
//
// Finally schedule a workitme to start reading packets from
// the target device. .
// 将读工作任务放入到windows的工作队列中
WdfWorkItemEnqueue(Adapter->ReadWorkItem);
//
// Post a request to get media change notification.
//
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = Adapter->IoTarget;
ntStatus = WdfRequestCreate(
WDF_NO_OBJECT_ATTRIBUTES,
Adapter->IoTarget,
&Adapter->StatusIndicationRequest);
if (!NT_SUCCESS(ntStatus)) {
DEBUGP(MP_ERROR, ("WdfRequestCreate for status-indication failed\n"));
NT_STATUS_TO_NDIS_STATUS(ntStatus, &Status);
break;
}
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = Adapter->StatusIndicationRequest;
ntStatus = WdfMemoryCreatePreallocated(&attributes,
&Adapter->NdisProtIndicateStatus,
sizeof(NDISPROT_INDICATE_STATUS),
&Adapter->WdfStatusIndicationBuffer);
if (!NT_SUCCESS(ntStatus)) {
DEBUGP(MP_ERROR, ("WdfMemoryCreatePreallocated failed 0x%x\n",
ntStatus));
NT_STATUS_TO_NDIS_STATUS(ntStatus, &Status);
break;
}
ntStatus = NICPostAsynchronousStatusIndicationRequest(Adapter);
if (!NT_SUCCESS(ntStatus)) {
DEBUGP(MP_ERROR, ("NICPostAsynchronousStatusIndicationRequest failed 0x%x\n",
ntStatus));
NT_STATUS_TO_NDIS_STATUS(ntStatus, &Status);
break;
}
} WHILE (FALSE);
if (!NT_SUCCESS(ntStatus)){
NICFreeSendResources(Adapter);
NICFreeRecvResources(Adapter);
}
//
// Signal the event so another thread can proceed.
//
NdisSetEvent(&Adapter->InitEvent);
DEBUGP(MP_TRACE, ("<-- NICInitAdapterWorker %x\n", ntStatus));
return ntStatus;
}
此函数所做事情如下:
1.NICOpenNdisProtocolInterface调用打开协议驱动接口。
2.NICAllocSendResources申请发送请求所使用的资源
3.NICAllocRecvResources创建读工作任务,申请读所需要的空间资源
4.通过OID查询一些Adapter程序并设置
5.WdfWorkItemEnqueue将读工作任务入工作队列
6.创建wdf请求
NICOpenNdisProtocolInterface函数细节如下:
//此函数作用是打开ndisprot协议驱动.
//首先生成远程IO并将其与协议驱动联系起来
//然后发送IOCTL代码等待绑定,IOCTL获取设备
//名随后打开设备,这样ndisprot就和ndisedge
//联系起来
NTSTATUS
NICOpenNdisProtocolInterface(
PMP_ADAPTER Adapter
)
/*++
Routine Description:
This routine opens the NDISPROT device and sends buch of IOCTLs
to get the names of NICs that it's bound to and pick the first
available one as our target phycial device.
Arguments:
Adapter Pointer to our adapter
Return Value:
NT Status code
--*/
{
UCHAR Buf[512];//should be enough to get name and description of one adapter
ULONG BufLength = sizeof(Buf);
ULONG i;
NTSTATUS status;
PWCHAR deviceName = NULL;
PWCHAR deviceDesc = NULL;
UNICODE_STRING fileName;
ULONG unUsed;
PNDISPROT_QUERY_BINDING pQueryBinding = NULL;
WDF_IO_TARGET_OPEN_PARAMS openParams;
PAGED_CODE();
DEBUGP(MP_TRACE, ("--> NICOpenNdisProtocolInterface\n"));
RtlInitUnicodeString ( &fileName, (PCWSTR) PROTOCOL_INTERFACE_NAME );
//生成一个远程IO目标
status = WdfIoTargetCreate(Adapter->WdfDevice, //小端口设备对象句柄
WDF_NO_OBJECT_ATTRIBUTES,
&Adapter->IoTarget);
if (!NT_SUCCESS(status)) {
DEBUGP(MP_ERROR, ("WdfIoTargetCreate failed 0x%x\n", status));
return status;
}
WDF_IO_TARGET_OPEN_PARAMS_INIT_CREATE_BY_NAME(&openParams,
&fileName,
STANDARD_RIGHTS_ALL
);
//将远程IO目标与远程设别对象联系起来
status = WdfIoTargetOpen(Adapter->IoTarget,
&openParams);
if (!NT_SUCCESS(status)) {
DEBUGP(MP_ERROR, ("WdfIoTargetOpen failed 0x%x\n", status));
return status;
}
DEBUGP (MP_INFO, ( "Successfully opened the protocol interface\n"));
//
// Make sure the target device supports direct I/O operations.
//
if (!((WdfIoTargetWdmGetTargetDeviceObject(Adapter->IoTarget))->Flags
& DO_DIRECT_IO)) {
ASSERTMSG("Target device doesn't support direct I/O\n", FALSE);
return STATUS_INVALID_DEVICE_REQUEST;
}
Adapter->FileObject = WdfIoTargetWdmGetTargetFileObject(Adapter->IoTarget);
//
// Wait for the NDISPROT to completly bind to all the miniport
// instances.
// 发送IOCTL请求
status = NICMakeSynchronousIoctl(
Adapter->IoTarget,
Adapter->FileObject,
IOCTL_NDISPROT_BIND_WAIT,
NULL,
0,
NULL,
0,
&unUsed
);
if (!NT_SUCCESS(status)) {
DEBUGP(MP_ERROR, ("IOCTL_NDISIO_BIND_WAIT failed, error %x\n", status));
return status;
}
pQueryBinding = (PNDISPROT_QUERY_BINDING)Buf;
//
// Query the binding of NDISPROT one at a time and see if you can open
// the bindings and use that as our physical adapter.
//
i = 0;
for (pQueryBinding->BindingIndex = i;
/* NOTHING */;
pQueryBinding->BindingIndex = ++i)
{
status = NICMakeSynchronousIoctl(
Adapter->IoTarget,
Adapter->FileObject,
IOCTL_NDISPROT_QUERY_BINDING,
pQueryBinding,
sizeof(NDISPROT_QUERY_BINDING),
Buf,
BufLength,
&unUsed
);
if (NT_SUCCESS(status))
{
deviceName = (PWCHAR)((PUCHAR)pQueryBinding +
pQueryBinding->DeviceNameOffset);
deviceDesc = (PWCHAR)((PUCHAR )pQueryBinding +
pQueryBinding->DeviceDescrOffset);
DEBUGP(MP_WARNING, ("%2d. %ws\n - %ws\n",
pQueryBinding->BindingIndex, deviceName, deviceDesc));
//
// Make sure we are not opening our device or another instance
// of NDISWDM miniport if more than one instances is installed.
// We can just use the AdapterDesc to decide this all the time.
// There is no need to get devicename - just an illustration.
//
if (wcscmp(deviceName, Adapter->AdapterName) &&
!wcsstr(deviceDesc, Adapter->AdapterDesc))
{
Adapter->FileObject->FsContext = NULL;
status = NICMakeSynchronousIoctl(
Adapter->IoTarget,
Adapter->FileObject,
IOCTL_NDISPROT_OPEN_DEVICE,
(PVOID)deviceName,
pQueryBinding->DeviceNameLength-sizeof(WCHAR),
NULL,
0,
&unUsed
);
if (!NT_SUCCESS(status)) {
DEBUGP(MP_ERROR, ("Failed to open NDIS Device %ws %x\n", deviceDesc, status));
} else {
DEBUGP(MP_WARNING, ("Successfully opened NDIS Device: %ws\n", deviceDesc));
break;
}
}
RtlZeroMemory(Buf, BufLength);
}
else
{
if (status != STATUS_NO_MORE_ENTRIES)
{
DEBUGP(MP_ERROR, ("EnumerateDevices: terminated abnormally, error %x\n", status));
}
break;
}
}
DEBUGP(MP_TRACE, ("<-- NICOpenNdisProtocolInterface\n"));
return status;
}
其中发送IOCTL_NDISPROT_QUERY_BINDING也只是获取协议驱动所绑定的网卡信息,而发送IOCTL_NDISPROT_OPEN_DEVICE才是真正指定打开上下文指定网卡,原理是:在协议驱动中通过网卡名称进而确定打开上下文。将指定IO目标(其实就是句柄的包装)所对应的irp的pFileObject->FsContext设置为pOpenContext,所以在发送或者接收函数中可以通过irp来获取打开上下文选择网卡就行发送。我们可能会疑惑发送或者接收irp怎么会保存上下文,其实只要确保了irp所对应的句柄不变,那么这个句柄所对应的不论是那个irp->pFileObject->FsContext都是确定的。

通过irp来获取打开上下文

//上层驱动程序发包时,最终会调用小端口的此函数
VOID
MPSendPackets(
IN NDIS_HANDLE MiniportAdapterContext,
IN PPNDIS_PACKET PacketArray,
IN UINT NumberOfPackets)
/*++
Routine Description:
Send Packet Array handler. Called by NDIS whenever a protocol
bound to our miniport sends one or more packets.
The input packet descriptor pointers have been ordered according
to the order in which the packets should be sent over the network
by the protocol driver that set up the packet array. The NDIS
library preserves the protocol-determined ordering when it submits
each packet array to MiniportSendPackets
As a deserialized driver, we are responsible for holding incoming send
packets in our internal queue until they can be transmitted over the
network and for preserving the protocol-determined ordering of packet
descriptors incoming to its MiniportSendPackets function.
A deserialized miniport driver must complete each incoming send packet
with NdisMSendComplete, and it cannot call NdisMSendResourcesAvailable.
Runs at IRQL <= DISPATCH_LEVEL
Arguments:
MiniportAdapterContext Pointer to our adapter context
PacketArray Set of packets to send
NumberOfPackets Length of above array
Return Value:
None
--*/
{
PMP_ADAPTER Adapter;
UINT PacketCount;
PNDIS_PACKET Packet = NULL;
NDIS_STATUS Status = NDIS_STATUS_FAILURE;
PTCB pTCB = NULL;
NTSTATUS ntStatus;
DEBUGP(MP_TRACE, ("---> MPSendPackets\n"));
Adapter = (PMP_ADAPTER)MiniportAdapterContext;
for(PacketCount=0; PacketCount < NumberOfPackets; PacketCount++)
{
//
// Check for a zero pointer
//
Packet = PacketArray[PacketCount];
if (!Packet){
ASSERTMSG("Packet pointer is NULL\n", FALSE);
continue;
}
DEBUGP(MP_LOUD, ("Adapter %p, Packet %p\n", Adapter, Packet));
Adapter->nPacketsArrived++;
if (MP_IS_READY(Adapter) && MP_TEST_FLAG(Adapter, fMP_POST_WRITES))
{
//
// Get a free TCB block
//
pTCB = (PTCB) NdisInterlockedRemoveHeadList(
&Adapter->SendFreeList,
&Adapter->SendLock);
if (pTCB == NULL)
{
DEBUGP(MP_LOUD, ("Can't allocate a TCB......!\n"));
Status = NDIS_STATUS_PENDING;
//
// Not able to get TCB block for this send. So queue
// it for later transmission and return NDIS_STATUS_PENDING .
//
NdisInterlockedInsertTailList(
&Adapter->SendWaitList,
(PLIST_ENTRY)&Packet->MiniportReserved[0],
&Adapter->SendLock);
}
else
{
NdisInterlockedIncrement(&Adapter->nBusySend);
ASSERT(Adapter->nBusySend <= NIC_MAX_BUSY_SENDS);
//
// Copy the content of the packet to a TCB block
// buffer. We need to do this because the target driver
// is not capable of handling chained buffers.
// 将包的数据拷贝到tcb中并插入到发送链表中
if (NICCopyPacket(Adapter, pTCB, Packet))
{
Adapter->nWritesPosted++;
//
// Post a asynchronouse write request.
//构建和发送写请求
ntStatus = NICPostWriteRequest(Adapter, pTCB);
if ( !NT_SUCCESS ( ntStatus ) ) {
DEBUGP (MP_ERROR, ( "NICPostWriteRequest failed %x\n", ntStatus ));
}
NT_STATUS_TO_NDIS_STATUS(ntStatus, &Status);
}else {
DEBUGP(MP_ERROR, ("NICCopyPacket returned error\n"));
Status = NDIS_STATUS_FAILURE;
}
}
}
if (!NT_SUCCESS(Status)) // yes, NT_SUCCESS macro can be used on NDIS_STATUS
{
DEBUGP(MP_LOUD, ("Calling NdisMSendComplete %x \n", Status));
NDIS_SET_PACKET_STATUS(Packet, Status);
//通知上层协议驱动发送完毕以便调用上层协议驱动的SendCompleteHandler
NdisMSendComplete(
Adapter->AdapterHandle,
Packet,
Status);
if (pTCB && (NdisInterlockedDecrement(&pTCB->Ref) == 0))
{
NICFreeSendTCB(Adapter, pTCB);
}
}
}
DEBUGP(MP_TRACE, ("<--- MPSendPackets\n"));
return;
}
根据函数参数NumberOfPackets我们能够确定此函数是可以发送一组包的。在小端口驱动程序中,还存在着只能发送单包的接口,如果这两个接口共存,则只有发送多包的起作用。
这个函数做的事情也简单,大致就是从发送链表中获取TCB,然后将包中的数据拷贝到TCB中,然后构建和发送写请求,循环重复上面的操作,直到所有的包发送完成。其中NICPostWriteRequest代码如下:
NTSTATUS
NICPostWriteRequest(
PMP_ADAPTER Adapter,
PTCB pTCB
)
/*++
Routine Description:
This routine posts a write Request to the target device to send
the network packet out to the device.
Arguments:
Adapter - pointer to the MP_ADAPTER structure
PTCB - Pointer to the TCB block that contains the Request and data.
Return Value:
NT status code
--*/
{
PIRP irp = NULL;
PIO_STACK_LOCATION nextStack;
NTSTATUS status = STATUS_SUCCESS;
DEBUGP(MP_TRACE, ("--> NICPostWriteRequest\n"));
// 准备一个写请求。实际上这里后面的参数不是OutputBuffer
// 而是InputBuffer。但是原始的代码注释就写成了OuputBuffer。
status = WdfIoTargetFormatRequestForWrite(
Adapter->IoTarget,
pTCB->Request,
NULL, //OutputBuffer
NULL, // OutputBufferOffsets
NULL); // StartingOffset
if (!NT_SUCCESS(status)) {
return status;
}
// 从WdfRequest获得IRP.
irp = WdfRequestWdmGetIrp(pTCB->Request);
// 获得下一个栈空间。然后填写输入缓冲。
nextStack = IoGetNextIrpStackLocation( irp );
irp->MdlAddress = pTCB->Mdl;
nextStack->Parameters.Write.Length = pTCB->ulSize;
// 设置一个完成回调函数。
WdfRequestSetCompletionRoutine(pTCB->Request,
NICWriteRequestCompletion,
pTCB);
// 发送请求。我们不关心发送请求的状态。因为请求
// 发送完毕之后我们从完成函数中获得请求完成状态。
if (WdfRequestSend(pTCB->Request, Adapter->IoTarget, WDF_NO_SEND_OPTIONS) == FALSE) {
status = WdfRequestGetStatus(pTCB->Request);
}
DEBUGP(MP_TRACE, ("<-- NICPostWriteRequest\n"));
return status;
}
函数做的事情是:1.WdfIoTargetFormatRequestForWrite准备写请求 2.从wdf请求获取irp并设置irp属性 3.WdfRequestSetCompletionRoutine设置wdf请求完成函数 4.WdfRequestSend发送写请求。
在驱动的特征中,我们并没有见到接收数据包的函数。其实接收工作是以工作任务的形式存在。接收工作任务的回调函数NICPostReadsWorkItemCallBack是在NICAllocRecvResources中注册的,而任务的开启是在NICInitAdapterWorker中。可见接收数据包的操作是在准备阶段就已经做的。
NICPostReadsWorkItemCallBack细节
VOID
NICPostReadsWorkItemCallBack(
WDFWORKITEM WorkItem
)
/*++
Routine Description:
Workitem to post all the free read requests to the target
driver. This workitme is scheduled from the MiniportInitialize
worker routine during start and also from the NICFreeRCB whenever
the outstanding read requests to the target driver goes below
the NIC_SEND_LOW_WATERMARK.
Arguments:
WorkItem - Pointer to workitem
Dummy - Unused
Return Value:
None.
--*/
{
PMP_ADAPTER Adapter;
NTSTATUS ntStatus;
PRCB pRCB=NULL;
DEBUGP(MP_TRACE, ("--->NICPostReadsWorkItemCallBack\n"));
//我们把工作任务的父对象设置为wdf设备句柄(适配器中保存的),
//现在我们通过这个句柄获得设备信息,设备信息中有适配器结构指针
Adapter = GetWdfDeviceInfo(WdfWorkItemGetParentObject(WorkItem))->Adapter;
NdisAcquireSpinLock(&Adapter->RecvLock);
while (MP_IS_READY(Adapter) && !IsListEmpty(&Adapter->RecvFreeList))
{
pRCB = (PRCB) RemoveHeadList(&Adapter->RecvFreeList);
ASSERT(pRCB);// cannot be NULL
//
// Insert the RCB in the recv busy queue
//
NdisInterlockedIncrement(&Adapter->nBusyRecv);
ASSERT(Adapter->nBusyRecv <= NIC_MAX_BUSY_RECVS);
InsertTailList(&Adapter->RecvBusyList, &pRCB->List);
NdisReleaseSpinLock(&Adapter->RecvLock);
Adapter->nReadsPosted++;
ntStatus = NICPostReadRequest(Adapter, pRCB);
NdisAcquireSpinLock(&Adapter->RecvLock);
if (!NT_SUCCESS ( ntStatus ) ) {
DEBUGP (MP_ERROR, ( "NICPostReadRequest failed %x\n", ntStatus ));
break;
}
}
NdisReleaseSpinLock(&Adapter->RecvLock);
//
// Clear the flag to let the WorkItem structure be reused for
// scheduling another one.
//
InterlockedExchange(&Adapter->IsReadWorkItemQueued, FALSE);
DEBUGP(MP_TRACE, ("<---NICPostReadsWorkItemCallBack\n"));
}
首先从接收链表中获取到RCB再将RCB插入到发送读请求链表中,最后调用NICPostReadRequest构建和发送读请求。
NICPostReadRequest代码如下,和上面发送写请求一样,不再赘述。
NTSTATUS
NICPostReadRequest(
PMP_ADAPTER Adapter,
PRCB pRCB
)
/*++
Routine Description:
This routine sends a read Request to the target device to get
the incoming network packet from the device.
Arguments:
Adapter - pointer to the MP_ADAPTER structure
pRCB - Pointer to the RCB block that contains the Request.
Return Value:
NT status code
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PIRP irp = NULL;
PIO_STACK_LOCATION nextStack;
DEBUGP(MP_LOUD, ("--> NICPostReadRequest\n"));
status = WdfIoTargetFormatRequestForRead(
Adapter->IoTarget,
pRCB->Request,
NULL, // InputBuffer
NULL, // InputBufferOffsets
NULL); // StartingOffset
if (!NT_SUCCESS(status)) {
return status;
}
irp = WdfRequestWdmGetIrp(pRCB->Request);
//
// Obtain a pointer to the stack location of the first driver that will be
// invoked. This is where the function codes and the parameters are set.
//
nextStack = IoGetNextIrpStackLocation( irp );
irp->MdlAddress = (PMDL) pRCB->Buffer;
nextStack->Parameters.Read.Length = NIC_BUFFER_SIZE;
pRCB->Ref = 1;
WdfRequestSetCompletionRoutine(pRCB->Request,
NICReadRequestCompletion,
pRCB);
//
// We are making an asynchronous request, so we don't really care
// about the return status of IoCallDriver.
//
if (WdfRequestSend(pRCB->Request, Adapter->IoTarget, WDF_NO_SEND_OPTIONS) == FALSE) {
status = WdfRequestGetStatus(pRCB->Request);
}
DEBUGP(MP_LOUD, ("<-- NICPostReadRequest\n"));
return status;
}
在这里向上层协议驱动提交包,并且如果包的引用为0,则释放包。
在读请求完成时,数据包才被完整接收到,此时可以操作
VOID
NICReadRequestCompletion(
IN WDFREQUEST Request,
IN WDFIOTARGET Target,
PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
IN WDFCONTEXT Context
)
/*++
Routine Description:
Completion routine for the read request. This routine
indicates the received packet from the WDM driver to
NDIS. This routine also handles the case where another
thread has canceled the read request.
Arguments:
DeviceObject - not used. Should be NULL
Request - Pointer to our read Request
Context - pointer to our adapter context structure
Return Value:
STATUS_MORE_PROCESSING_REQUIRED - because this is an asynchronouse Request
and we want to reuse it.
--*/
{
PRCB pRCB = (PRCB)Context;
PMP_ADAPTER Adapter = pRCB->Adapter;
ULONG bytesRead = 0;
BOOLEAN bIndicateReceive = FALSE;
NTSTATUS status;
UNREFERENCED_PARAMETER(Target);
UNREFERENCED_PARAMETER(Request);
DEBUGP(MP_TRACE, ("--> NICReadRequestCompletion\n"));
status = CompletionParams->IoStatus.Status;
if (!NT_SUCCESS(status)) {
Adapter->nReadsCompletedWithError++;
DEBUGP (MP_LOUD, ("Read request failed %x\n", status));
//
// Clear the flag to prevent any more reads from being
// posted to the target device.
//
MP_CLEAR_FLAG(Adapter, fMP_POST_READS);
} else {
Adapter->GoodReceives++;
bytesRead = (ULONG)CompletionParams->IoStatus.Information;
DEBUGP (MP_VERY_LOUD, ("Read %d bytes\n", bytesRead));
Adapter->nBytesRead += bytesRead;
bIndicateReceive = TRUE;
}
if (bIndicateReceive) {
//向上层提交包
NICIndicateReceivedPacket(pRCB, bytesRead);
}
if (NdisInterlockedDecrement(&pRCB->Ref) == 0)
{
NICFreeRCB(pRCB);
}
return;
}
这里向上提交包是小端口驱动向上通知包已经接收到。
NDIS小端口驱动:等您坐沙发呢!
发表评论
