NDIS_MINIPORT_CHARACTERISTICS
這個資料結構只可以用在 NDIS 5.x 之前,之後已經被 NDIS 6.0 禁用了,不過 NDIS 6.0 是換湯不換藥啦,所以瞭解這個並無不妥。
typedef struct _NDIS_MINIPORT_CHARACTERISTICS {
UCHAR MajorNdisVersion;
UCHAR MinorNdisVersion;
UINT Reserved;
W_CHECK_FOR_HANG_HANDLER CheckForHangHandler;
W_DISABLE_INTERRUPT_HANDLER DisableInterruptHandler;
W_ENABLE_INTERRUPT_HANDLER EnableInterruptHandler;
W_HALT_HANDLER HaltHandler;
W_HANDLE_INTERRUPT_HANDLER HandleInterruptHandler;
W_INITIALIZE_HANDLER InitializeHandler;
W_ISR_HANDLER ISRHandler;
W_QUERY_INFORMATION_HANDLER QueryInformationHandler;
W_RECONFIGURE_HANDLER ReconfigureHandler;
W_RESET_HANDLER ResetHandler;
W_SEND_HANDLER SendHandler;
W_SET_INFORMATION_HANDLER SetInformationHandler;
W_TRANSFER_DATA_HANDLER TransferDataHandler;
//
// Version used is V4.0 or V5.0
// with following members
//
W_RETURN_PACKET_HANDLER ReturnPacketHandler;
W_SEND_PACKETS_HANDLER SendPacketsHandler;
W_ALLOCATE_COMPLETE_HANDLER AllocateCompleteHandler;
//
// Version used is V5.0 with the following members
//
W_CO_CREATE_VC_HANDLER CoCreateVcHandler;
W_CO_DELETE_VC_HANDLER CoDeleteVcHandler;
W_CO_ACTIVATE_VC_HANDLER CoActivateVcHandler;
W_CO_DEACTIVATE_VC_HANDLER CoDeactivateVcHandler;
W_CO_SEND_PACKETS_HANDLER CoSendPacketsHandler;
W_CO_REQUEST_HANDLER CoRequestHandler;
//
// Version used is V5.1 with the following members
//
W_CANCEL_SEND_PACKETS_HANDLER CancelSendPacketsHandler;
W_PNP_EVENT_NOTIFY_HANDLER PnPEventNotifyHandler;
W_MINIPORT_SHUTDOWN_HANDLER AdapterShutdownHandler;
} NDIS_MINIPORT_CHARACTERISTICS, *PNDIS_MINIPORT_CHARACTERISTICS;
基本上, W_CO_XXX 不用理會,直接給
NULL,用不到啊。從這個資料結構看來,NDIS 也是一步一步發展而來的。不過 NDIS 6.0 卻來個天翻地覆的改變。
需要實做的大概只有紅色的函式,如果 NIC 不會產生 interrupt 的話。ISR 相關的東西在下一集說明。
CheckForHangHandler
系統預設每兩秒會呼叫這個函式,如果回傳 TRUE,表示 NIC 已經不正常了,接著系統會呼叫 MiniportReset。
你可以在呼叫 NdisMSetAttributesEx() 的時候,修改系統呼叫這個函式的間隔。
BOOLEAN MiniportCheckForHang(
IN NDIS_HANDLE MiniportAdapterContext
);
MiniportCheckForHang runs at IRQL = DISPATCH_LEVEL.
DisableInterruptHandler
系統呼叫這個函式,命令你的 Driver 關掉 NIC 的 interrutp。如果 NIC 支援動態開關 interrupt 但不共享 IRQ的話,Driver 可以提供這個函式。這個函式只是讓系統有機會開關你的 NIC 的 interrupt,換句話說,你也可以不提供。
VOID MiniportDisableInterrupt(
IN NDIS_HANDLE MiniportAdapterContext
);
DIRQL
EnableInterruptHandler
系統呼叫這個函式,命令你的 Driver 打開 NIC 的 interrupt。如果 NIC 支援動態開關 interrupt 但不共享 IRQ的話,Driver 可以提供這個函式。這個函式只是讓系統有機會開關你的 NIC 的 interrupt,換句話說,你也可以不提供。
VOID MiniportEnableInterrupt(
IN NDIS_HANDLE MiniportAdapterContext
);
DIRQL
HaltHandler
NDIS 會呼叫 MiniportHalt 的時機:
- NDIS 無法設定 Multicast Address 或 MAC_OPTION。
- Unbind 所有 bind 到 Miniport Driver 的 protocol drivers 之後。
- 在 Driver Unload 的時候。
- 系統關機的時候。
當 NIC 被移除或停用時,會呼叫這個函式。這個函式必須 deallocate 所有在 MiniportInitialize() 所 allocate 的資源。MiniportHalt() 在呼叫 NdisMDeregisterInterrupt() 都有可能被 MiniportISR() 或 MiniportHandleInterrupt() 所中斷,因此,儘快在一開使便關掉 interrupt,然後呼叫NdisMDeregisterInterrupt()。
Driver 如果有收到來自 NIC 的封包,並且已經往上層回報,但是 NDIS 還沒呼叫 MinoportReturnPacket() 的話, MiniportHalt 必須等到所有封包的 MinoportReturnPacket() 都已經被呼叫過,才可以返回。
VOID MiniportHalt(IN NDIS_HANDLE MiniportAdapterContext);
MiniportHalt runs at IRQL = PASSIVE_LEVEL.
HandleInterruptHandler
DSR(Deferred Service Routine)。如果 NIC 共享 IRQ 的話,只有當 MiniportISR() 把 QueueMiniportHandleInterrupt 設為 TRUE 的時候,系統之後才會呼叫 MiniportHandleInterrupt()。
通常在這裡面會做的事情有:
- 1. 取得 NIC 的 Interrupt Event。
- 2. 根據 NIC 的 Interrupt Event 採取對應的動作。大部分的動作都是去取得 NIC 上的封包。
- 3. 清掉 NIC 的 Interrupt。
- 4. Enable Interrupt Mask。因為這時候 Interrupt Mask 通常是關掉的。
為甚麼事情不在 MiniportISR() 做完就好了?那是因為很多事情很花時間,很不幸的,ISR 是執行在 DIRQL,這意味著如果 ISR 不趕快結束的話,其他執行在 DIRQL 的 Thread 根本就無法執行,系統可能會因此而 Hang 住,因為系統有些 Thread 也是在 DIRQL 上執行的。
VOID MiniportHandleInterrupt(
IN NDIS_HANDLE MiniportAdapterContext
);
MiniportHandleInterrupt runs at IRQL = DISPATCH_LEVEL.
InitializeHandler
MiniportInitialize() 是 Driver 的 DriverEntry() 離開之後會被系統呼叫的函式。它要做的事情很多,包括檢查 Medium Type 是不是符合(通常是 NdisMedium802_3)、初始化所有的資源(如 Timer 、SpinLock、Memory...等等)、讀取 Registry、初始化 NIC。
MiniportInitialize() 的傳入值 MiniportAdapterHandle 跟 WrapperConfigurationContext,要把它們記起來,以後常常會用到。
如果 NIC 是屬於 PCI 之類的介面的話,Driver 需要呼叫 NdisMQueryAdapterResources(),取得 NIC 的硬體資源。一般的硬體資源不外乎 Interrupt、DMA、Port(IoAddress)。
VOID NdisMQueryAdapterResources(
OUT PNDIS_STATUS Status,
IN NDIS_HANDLE WrapperConfigurationContext,
OUT PNDIS_RESOURCE_LIST ResourceList,
IN OUT PUINT BufferSize
);
ResourceList 是 Driver 要提供的空間(不固定大小)。NDIS_RESOURCE_LIST 也就是 CM_PARTIAL_RESOURCE_LIST:
typedef struct _CM_PARTIAL_RESOURCE_LIST {
USHORT Version;
USHORT Revision;
ULONG Count;
CM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptors[1];
} CM_PARTIAL_RESOURCE_LIST, *PCM_PARTIAL_RESOURCE_LIST;
Count:PartialDescriptors 陣列有幾個元素。
typedef struct _CM_PARTIAL_RESOURCE_DESCRIPTOR {
UCHAR Type;
UCHAR ShareDisposition;
USHORT Flags;
union {
struct {
PHYSICAL_ADDRESS Start;
ULONG Length;
} Generic;
struct {
PHYSICAL_ADDRESS Start;
ULONG Length;
} Port;
struct {
ULONG Level;
ULONG Vector;
KAFFINITY Affinity;
} Interrupt;
struct {
PHYSICAL_ADDRESS Start;
ULONG Length;
} Memory;
struct {
ULONG Channel;
;
ULONG Reserved1;
} Dma;
struct {
ULONG Data[3];
} DevicePrivate;
struct {
ULONG Start;
ULONG Length;
ULONG Reserved;
} BusNumber;
struct {
ULONG DataSize;
ULONG Reserved1;
ULONG Reserved2;
} DeviceSpecificData;
} u;
} CM_PARTIAL_RESOURCE_DESCRIPTOR, *PCM_PARTIAL_RESOURCE_DESCRIPTOR;
Driver 需要做的事情,就是把這些硬體資訊記起來,以後會用到。
如果 NIC 支援 Port 資源的話,Driver 需要呼叫 NdisMRegisterIoPortRange(),之後才可以使用NdisRawReadPortXxx() 以及 NdisRawWritePortXxx()。
這就是所謂的 PortMapping?
NDIS_STATUS NdisMRegisterIoPortRange(
OUT PVOID *PortOffset,
IN NDIS_HANDLE MiniportAdapterHandle,
IN UINT InitialPort,
IN UINT NumberOfPorts
);
PCMCIA 卡可能會有所謂的 Atrribute Memory,Driver 可以使用 NdisReadPcmciaAttributeMemory() 來讀取:
ULONG NdisReadPcmciaAttributeMemory(
IN NDIS_HANDLE NdisAdapterHandle,
IN ULONG Offset,
IN PVOID Buffer,
IN ULONG Length
);
接下來便是讀取 Registry。首先 Driver 先呼叫 NdisOpenConfiguration(),取得 Configuration Handle:
VOID NdisOpenConfiguration(
OUT PNDIS_STATUS Status,
OUT PNDIS_HANDLE ConfigurationHandle,
IN NDIS_HANDLE WrapperConfigurationContext
);
接下來 Driver 便可以快快樂樂用這個 Configuration Handle 來讀取或寫入 Registry:
VOID NdisReadConfiguration(
OUT PNDIS_STATUS Status,
OUT PNDIS_CONFIGURATION_PARAMETER *ParameterValue,
IN NDIS_HANDLE ConfigurationHandle,
IN PNDIS_STRING Keyword,
IN NDIS_PARAMETER_TYPE ParameterType
);
VOID NdisWriteConfiguration(
OUT PNDIS_STATUS Status,
IN NDIS_HANDLE ConfigurationHandle,
IN PNDIS_STRING Keyword,
IN PNDIS_CONFIGURATION_PARAMETER ParameterValue
);
最後,再把這個 Configuration Handle 還給系統:
VOID NdisCloseConfiguration(
IN NDIS_HANDLE ConfigurationHandle
);
在 MiniportInitialize() 裡面,Driver 一定要呼叫 NdisMSetAttributesEx()。其函式原型如下:
VOID NdisMSetAttributesEx(
IN NDIS_HANDLE MiniportAdapterHandle,
IN NDIS_HANDLE MiniportAdapterContext,
IN UINT CheckForHangTimeInSeconds OPTIONAL,
IN ULONG AttributeFlags,
IN NDIS_INTERFACE_TYPE AdapterType
);
MiniportAdapterContext 很重要。要知道在 NDIS 或是 WDM 裡面,Driver 永遠都只有被呼叫的份,那表示 Driver 得維護自己的狀態才行,不然系統隨便呼叫 Driver 的函式,Driver 自己又不知道自己的狀態,那不是很容易就 BSOD (Blue Screen Of Death)了嗎。所以 MiniportAdapterContext 就是讓 Driver 提供一個地方,讓系統每次呼叫 Driver 的函式時,都會把這個地方的位址傳給 Driver,如此 Driver 就可以記住跟知道它需要的資訊。
CheckForHangTimeInSeconds 是告訴系統,多久來呼叫 Driver 的 MiniportCheckForHang()。
AttributeFlags 很重要。NDIS_ATTRIBUTE_BUS_MASTER、NDIS_ATTRIBUTE_DESERIALIZE、 NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND、NDIS_ATTRIBUTE_SURPRISE_REMOVE_OK 值得注意。
- NDIS_ATTRIBUTE_SURPRISE_REMOVE_OK 表示這隻 Driver 可以處理 NIC 突然被移除,並且不需要通知使用者。這樣的 Driver 必須實做 MiniportPnPEventNotify ()。
- NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND 表示系統轉移到低耗電的狀態(Sleeping)之前,NDIS 並不需要呼叫這隻 Driver 的 MiniportHalt()。
AdapterType 很有趣,基本上它是告訴 NDIS,這隻 Driver 的 Bus Type,也就是它下層要面對的對象是誰。
NDIS_INTERFACE_TYPE 的定義如下(from ndis.h):
typedef enum _NDIS_INTERFACE_TYPE {
NdisInterfaceInternal = Internal,
NdisInterfaceIsa = Isa,
NdisInterfaceEisa = Eisa,
NdisInterfaceMca = MicroChannel,
NdisInterfaceTurboChannel = TurboChannel,
NdisInterfacePci = PCIBus,
NdisInterfacePcMcia = PCMCIABus
} NDIS_INTERFACE_TYPE, *PNDIS_INTERFACE_TYPE;
只有 ISA、EISA、PCI、PCMCIA,那 USB 跟 SDIO 勒?請使用
100。
Windows CE 如果 Miniport Driver 沒有實做 MiniportReset() 的話,最好在 NdisMSetAttributesEx() 內把 CheckForHangInSeconds 設定為一個很大的數值,這樣系統就不會常常醒來呼叫 MiniportCheckForHang(),進而達到省電的目的。
CheckForHangInSeconds 也可以透過
HKEY_LOCAL_MACHINE\Comm\
\Parms\CheckForHangTimeInSeconds來設定。
呼叫完 NdisMSetAttributesEx() 之後,如果 Driver 需要處理 Interrupt,請呼叫 NdisMRegisterInterrupt():
NDIS_STATUS NdisMRegisterInterrupt(
OUT PNDIS_MINIPORT_INTERRUPT Interrupt,
IN NDIS_HANDLE MiniportAdapterHandle,
IN UINT InterruptVector,
IN UINT InterruptLevel,
IN BOOLEAN RequestIsr,
IN BOOLEAN SharedInterrupt,
IN NDIS_INTERRUPT_MODE InterruptMode
);
Interrupt:Driver 要保存下來,所有要跟 ISR 同步存取 NIC 資源的函式都得使用到這個 Handle(如 NdisMSynchronizeWithInterrupt())。
InterruptVector: 把之前取得的 Interrupt Vector 填上去就對了。
InterruptLevel: 把之前取得的 Interrupt Level 填上去就對了。
RequestIsr:如果 Driver 希望 NIC 每次產生 Interrupt 的時候,系統都會呼叫到 MiniportISR() 的話,設為 TRUE。如果 SharedInterrupt = TRUE,RequestIsr = TRUE。
SharedInterrupt:表示是否要共享 Interrutp Line。
InterruptMode:可以指定 Interrupt 的偵測是 Level Trigger 或 Edge Trigger。
最後,在 NDIS5.0 之前的版本,Driver 還必須 呼叫 NdisMRegisterAdapterShutdownHandler() ,註冊系統關機時要呼叫的函式。
VOID NdisMRegisterAdapterShutdownHandler(
IN NDIS_HANDLE MiniportHandle,
IN PVOID ShutdownContext,
IN ADAPTER_SHUTDOWN_HANDLER ShutdownHandler
);
NDIS_STATUS MiniportInitialize(
OUT PNDIS_STATUS OpenErrorStatus,
OUT PUINT SelectedMediumIndex,
IN PNDIS_MEDIUM MediumArray,
IN UINT MediumArraySize,
IN NDIS_HANDLE MiniportAdapterHandle,
IN NDIS_HANDLE WrapperConfigurationContext
);
MiniportInitialize runs at IRQL = PASSIVE_LEVEL.
ISRHandler
如果要系統呼叫這個函式的話,Driver 必須在 MiniportInitialize() 內呼叫 NdisMRegisterInterrupt()。
VOID MiniportISR(
OUT PBOOLEAN InterruptRecognized,
OUT PBOOLEAN QueueMiniportHandleInterrupt,
IN NDIS_HANDLE MiniportAdapterContext
);
InterruptRecognized:如果這個 interrupt 是這隻 Driver 的 NIC 所產生的話,回傳 TRUE。在共享 IRQ 的狀況下,有可能會回傳 FALSE。
QueueMiniportHandleInterrupt:回傳 TRUE的話,表示系統會在離開 MiniportISR() 之後再呼叫 MiniportHandleInterrupt()。
MiniportISR runs at DIRQL.
QueryInformationHandler
系統會透過 MiniportQueryInformation() 取得 NIC 的資訊,如 MAC address、TX/RX 統計數字、連線速度等等。
NDIS_STATUS MiniportQueryInformation(
IN NDIS_HANDLE MiniportAdapterContext,
IN NDIS_OID Oid,
IN PVOID InformationBuffer,
IN ULONG InformationBufferLength,
OUT PULONG BytesWritten,
OUT PULONG BytesNeeded
);
MiniportQueryInformation runs at IRQL = DISPATCH_LEVEL.
ReconfigureHandler
NDIS 不會呼叫 MiniportReconfigure() ,但是 Driver 可以在 MiniportInitialize() 內呼叫它。
NDIS_STATUS MiniportReconfigure(
OUT PNDIS_STATUS OpenErrorStatus,
IN NDIS_HANDLE MiniportAdapterContext,
IN NDIS_HANDLE WrapperConfigurationContext
);
MiniportReconfigure runs at the same IRQL as MiniportIntialize.
ResetHandler
NDIS 會呼叫 MiniportReset 的時機:
- MiniportCheckForHang() 回傳 TRUE。
- NDIS 偵測到 Pending Sending Packet(for serialized miniport drivers only)。
- NDIS 偵測到在某段時間內 Pending Sending Packet 一直無法被 complete。
Driver 必須在 MiniportReset() Reset NIC 跟 Driver 的狀態。
Deserialized Miniport Driver 必須 Complete 任何尚未送到 NIC 的 Ndis Packets。
Miniport Driver 不需要呼叫 NdisMIndicateStatus() 或 NdisMCoIndicateStatus()。
如果 Driver 有支援 MiniportCheckForHang() 的話,系統會定期呼叫 MiniportCheckForHang(),再決定是否要呼叫 MiniportReset()。如果 Driver 不支援 MiniportCheckForHang() 的話,系統會在呼叫 MiniportQueryInformation()、MiniportSetInformation()、MiniportSendPackets()、MiniportSend() 或 MiniportWanSend 逾時之後再呼叫 MiniportReset()。
NDIS_STATUS MiniportReset(
OUT PBOOLEAN AddressingReset,
IN NDIS_HANDLE MiniportAdapterContext
);
AddressingReset:如果 Driver 需要在 MiniportReset() 返回之後,讓 NDIS 透過 MiniportSetInformation() 重新設定 NIC 跟 Driver 的狀態(如 Packet Filter、Multicass Address)的話,設為 TRUE。
MiniportReset runs at IRQL = DISPATCH_LEVEL.
SendHandler
如果 Driver 不支援 MiniportSendPackets()、MiniportWanSend()、或 MiniportCoSendPackets() 的話,Driver 必須支援 SendHandler()。如果 Driver 支援 MiniportSendPackets() 跟 MiniportSend() 的話,系統將不會呼叫 MiniportSend()。
如果是 Deserialized Miniport Driver 的話,Driver 通常會把 Packet 放到 software queue 中,然後回傳 NDIS_STATUS_PENDING。等到 Driver 把 software queue 內的 Packet 送到 NIC 之後,再呼叫 NdisMSendComplete(),如此才算是完成整個 TX。
NDIS_STATUS MiniportSend(
IN NDIS_HANDLE MiniportAdapterContext,
IN PNDIS_PACKET Packet,
IN UINT Flags
);
Packet:Driver 不可以直接存取這個參數。Driver 可以呼叫 NdisQueryPacket() 取得 Packet 的資訊。
typedef struct _NDIS_PACKET {
NDIS_PACKET_PRIVATE Private;
union {
struct {
UCHAR MiniportReserved[2*sizeof(PVOID)];
UCHAR WrapperReserved[2*sizeof(PVOID)];
};
struct {
UCHAR MiniportReservedEx[3*sizeof(PVOID)];
UCHAR WrapperReservedEx[sizeof(PVOID)];
};
struct {
UCHAR MacReserved[4*sizeof(PVOID)];
};
};
ULONG_PTR Reserved[2];
UCHAR ProtocolReserved[1];
} NDIS_PACKET, *PNDIS_PACKET, **PPNDIS_PACKET;
Private:
typedef struct _NDIS_PACKET_PRIVATE {
UINT PhysicalCount; // number of physical pages in packet.
UINT TotalLength; // Total amount of data in the packet.
PNDIS_BUFFER Head; // first buffer in the chain
PNDIS_BUFFER Tail; // last buffer in the chain
// if Head is NULL the chain is empty; Tail doesn't have to be NULL also
PNDIS_PACKET_POOL Pool; // so we know where to free it back to
UINT Count;
ULONG Flags;
BOOLEAN ValidCounts;
UCHAR NdisPacketFlags; // See fPACKET_xxx bits below
USHORT NdisPacketOobOffset;
} NDIS_PACKET_PRIVATE, * PNDIS_PACKET_PRIVATE;
VOID NdisQueryPacket(
IN PNDIS_PACKET _Packet,
OUT PUINT _PhysicalBufferCount OPTIONAL,
OUT PUINT _BufferCount OPTIONAL,
OUT PNDIS_BUFFER * _FirstBuffer OPTIONAL,
OUT PUINT _TotalPacketLength OPTIONAL
)
{
if ((_FirstBuffer) != NULL) {
PNDIS_BUFFER * __FirstBuffer = (_FirstBuffer);
*(__FirstBuffer) = (_Packet)->Private.Head;
}
if ((_TotalPacketLength != NULL) || (_BufferCount != NULL) || (_PhysicalBufferCount != NULL)) {
if (!(_Packet)->Private.ValidCounts) {
PNDIS_BUFFER TmpBuffer = (_Packet)->Private.Head;
UINT PTotalLength = 0, PPhysicalCount = 0, PAddedCount = 0;
UINT PacketLength, Offset;
while (TmpBuffer != (PNDIS_BUFFER)NULL) {
NdisQueryBufferOffset(TmpBuffer, &Offset, &PacketLength);
PTotalLength += PacketLength;
PPhysicalCount += (UINT)NDIS_BUFFER_TO_SPAN_PAGES(TmpBuffer);
++PAddedCount;
TmpBuffer = TmpBuffer->Next;
}
(_Packet)->Private.Count = PAddedCount;
(_Packet)->Private.TotalLength = PTotalLength;
(_Packet)->Private.PhysicalCount = PPhysicalCount;
(_Packet)->Private.ValidCounts = TRUE;
}
if (_PhysicalBufferCount != NULL) {
PUINT __PhysicalBufferCount = (_PhysicalBufferCount);
*(__PhysicalBufferCount) = (_Packet)->Private.PhysicalCount;
}
if (_BufferCount != NULL) {
PUINT __BufferCount = (_BufferCount);
*(__BufferCount) = (_Packet)->Private.Count;
}
if (_TotalPacketLength != NULL) {
PUINT __TotalPacketLength = (_TotalPacketLength);
*(__TotalPacketLength) = (_Packet)->Private.TotalLength;
}
}
}
PhysicalBufferCount:這個 Ndis Packet 包含幾個 Page。
BufferCount:這個 Ndis Packet 包含幾個 MDL。
FirstBuffer:第一個 MDL 的位址。
TotalPacketLength:全部 MDL 所描述的記憶體大小。
直接看程式碼,NDIS_PACKET 的秘密可說破解一半,同時也把 MDL 的資料結構給搞清楚。NdisQueryPacket() 會找出它所包含的所有 MDL,然後透過 NdisQueryBufferOffset() 查詢每個 MDL 的大小,接著把這些資訊紀錄在 NDIS_PACKET_PRIVATE 內。
請注意,NDIS_BUFFER 是一個 MDL(Memory Description List),也因為它是個 List,這表示它有可能是由數個記憶體區塊串起來而形成的,因此 Driver 是不可以直接存取它的。所以 Driver 必須透過 NdisQueryBufferSafe() 來取得真正的記憶體位址,並且用 NdisGetNextBuffer() 取得下個 MDL。如此便可以把整個 MDL 的內容複製到一塊連續的記憶體中,整個送給 NIC。有的 Bus Type 可能不需要這個動作,因為它們的 Bus Driver 支援 MDL 的寫入方式。
VOID NdisGetNextBuffer(IN PNDIS_BUFFER CurrentBuffer, OUT PNDIS_BUFFER *NextBuffer) {
*NextBuffer = CurrentBuffer->Next;
}
VOID NdisQueryBufferSafe(
IN PNDIS_BUFFER Buffer,
OUT PVOID *VirtualAddress OPTIONAL,
OUT PUINT Length,
IN MM_PAGE_PRIORITY Priority
) {
if ((CHAR *)VirtualAddress != (CHAR *)NULL) {
*(PVOID *)(_VirtualAddress) = MmGetSystemAddressForMdlSafe(Buffer, Priority);
}
*_Length = MmGetMdlByteCount(Buffer);
}
除此之外,Driver 可以透過 NDIS_PER_PACKET_INFO_FROM_PACKET() 取得 Packet 的額外資訊。Driver 可以拿它來判斷 Packet 是否支援 802.1Q(這個我也不知道是什麼,網路的東西太多了,學不完啊)。
PVOID NDIS_PER_PACKET_INFO_FROM_PACKET(
IN/OUT PNDIS_PACKET Packet,
IN NDIS_PER_PACKET_INFO InfoType
);
typedef enum _NDIS_PER_PACKET_INFO {
TcpIpChecksumPacketInfo,
IpSecPacketInfo,
TcpLargeSendPacketInfo,
ClassificationHandlePacketInfo,
HeaderIndexInfo, // Internal NDIS use only
ScatterGatherListPacketInfo,
Ieee8021pPriority,
OriginalPacketInfo,
NdisInternalExtension1, // Internal NDIS use only
NdisInternalExtension2, // Internal NDIS use only
NdisInternalExtension3, // Internal NDIS use only
NdisInternalPktDebug, // Internal NDIS use only
MaxPerPacketInfo
} NDIS_PER_PACKET_INFO, *PNDIS_PER_PACKET_INFO;
typedef struct _NDIS_PACKET_8021Q_INFO {
union {
struct {
UINT32 UserPriority:3;
UINT32 CanonicalFormatId:1;
UINT32 VlanId:12;
UINT32 Reserved:16;
} TagHeader;
PVOID Value;
};
} NDIS_PACKET_8021Q_INFO, *PNDIS_PACKET_8021Q_INFO;
The MiniportSend function of a serialized miniport driver runs at IRQL = DISPATCH_LEVEL.
SetInformationHandler
系統會透過 MiniportSetInformation() 設定 NIC 的資訊,如 Packet Filter、Protocol Option 等等。
NDIS_STATUS MiniportSetInformation(
IN NDIS_HANDLE MiniportAdapterContext,
IN NDIS_OID Oid,
IN PVOID InformationBuffer,
IN ULONG InformationBufferLength,
OUT PULONG BytesRead,
OUT PULONG BytesNeeded
);
MiniportSetInformation runs at IRQL = DISPATCH_LEVEL.
TransferDataHandler
NDIS_STATUS MiniportTransferData(
OUT PNDIS_PACKET Packet,
OUT PUINT BytesTransferred,
IN NDIS_HANDLE MiniportAdapterContext,
IN NDIS_HANDLE MiniportReceiveContext,
IN UINT ByteOffset,
IN UINT BytesToTransfer
);
ReturnPacketHandler
當 Driver 透過 NdisMIndicateReceivePacket() 或 NdisMCoIndicateReceivePacket() 告訴 NDIS 有封包由 NIC 收進來之後,因為 RX 的 Ndis Packet 是由 Driver 所配置,所以當 NDIS 把封包丟給 Application 之後,會呼叫 MinoportReturnPacket() ,讓 Driver 把這些 Ndis Packet 釋放或再利用。
Driver 必須在 MinoportReturnPacket() 裡面呼叫 NdisUnchainBufferAtXxx(),然後利用 NdisFreeBuffer() 釋放掉取得的 NDIS_BUFFER。最後如果你不要重新利用這個 NDIS_PACKET 的話,再呼叫 NdisFreePacket() 把這個 Packet 釋放掉。如果 Driver 要重新利用這個 NDIS_PACKET 的話,呼叫NdisReinitializePacket()。
這樣說明似乎有點模糊,這牽涉到 NDIS_PACKET 的配置。首先,NDIS_PACKET 的定義如下:
typedef struct _NDIS_PACKET {
NDIS_PACKET_PRIVATE Private;
union {
struct {
UCHAR MiniportReserved[2*sizeof(PVOID)];
UCHAR WrapperReserved[2*sizeof(PVOID)];
};
struct {
UCHAR MiniportReservedEx[3*sizeof(PVOID)];
UCHAR WrapperReservedEx[sizeof(PVOID)];
};
struct {
UCHAR MacReserved[4*sizeof(PVOID)];
};
};
ULONG_PTR Reserved[2];
UCHAR ProtocolReserved[1];
} NDIS_PACKET, *PNDIS_PACKET, **PPNDIS_PACKET;
當 Driver 由 NIC 收到封包時,Driver 需要呼叫 NdisAllocatePacket() 配置一個 NDIS_PACKET。
VOID NdisAllocatePacket(
OUT PNDIS_STATUS Status,
OUT PNDIS_PACKET *Packet,
IN NDIS_HANDLE PoolHandle
);
問題來啦,PoolHandle 是甚麼?可以吃嗎?所以 Driver 之前得在 MiniportInitialize() 裡面透過 NdisAllocatePacketPool() 配置一個 Packet Pool 才行:
VOID NdisAllocatePacketPool(
OUT PNDIS_STATUS Status,
OUT PNDIS_HANDLE PoolHandle,
IN UINT NumberOfDescriptors,
IN UINT ProtocolReservedLength
);
ProtocolReservedLength:Miniport Driver 必須把它設為 PROTOCOL_RESERVED_SIZE_IN_PACKET。
有了 NDIS_PACKET 了,但是 Driver 還沒把 NDIS_BUFFER 串到 NDIS_PACKET。所以 Driver 得呼叫 NdisChainBufferAtBack() 或 NdisChainBufferAtFront() 把 NDIS_BUFFER 給串到 NDIS_PACKET。
等等,NDIS_BUFFER 還沒配置呢。所以 Driver 得先呼叫 NdisAllocateBuffer ():
VOID NdisAllocateBuffer(
OUT PNDIS_STATUS Status,
OUT PNDIS_BUFFER *Buffer,
IN NDIS_HANDLE PoolHandle,
IN PVOID VirtualAddress,
IN UINT Length
);
VirtualAddress:收到的封包的位址。
PoolHandle:Again,又遇到 PoolHandle 的問題。所以 Driver 得在 MiniportInitialize() 裡面先透過 NdisAllocateBufferPool() 把 Buffer Pool 配置好。
VOID NdisAllocateBufferPool(
OUT PNDIS_STATUS Status,
OUT PNDIS_HANDLE PoolHandle,
IN UINT NumberOfDescriptors
);
所以事情比較清楚了。因為要把封包丟給 NDIS 之前,Driver 必須先配置 NDIS_PACKET,而 NDIS_PACKET 又包含一個以上的 NDIS_BUFFER,所以在 MinoportReturnPacket() 裡面,就算你要重新利用 NDIS_PACKET,你還是得先把裡面的 NDIS_BUFFER全部移除並釋放。因為 MDL映射到的 Virtual Address 有可能已經都不對了。
VOID MinoportReturnPacket(
IN NDIS_HANDLE MiniportAdapterContext,
IN PNDIS_PACKET Packet
);
MiniportReturnPacket runs at IRQL = DISPATCH_LEVEL.
SendPacketsHandler
跟 MiniportSend() 相同,只不過它可以一次處理多個 NDIS_PACKET。
VOID MiniportSendPackets(
IN NDIS_HANDLE MiniportAdapterContext,
IN PPNDIS_PACKET PacketArray,
IN UINT NumberOfPackets
);
AllocateCompleteHandler
Bus-Master DMA NIC 的 Driver 在呼叫 NdisMAllocateSharedMemoryAsync() 才需要的函式。因為我也沒寫過這類 Driver,所以也無從解釋。
VOID MiniportAllocateComplete(
IN NDIS_HANDLE MiniportAdapterContext,
IN PVOID VirtualAddress,
IN PNDIS_PHYSICAL_ADDRESS PhysicalAddress,
IN ULONG Length,
IN PVOID Context
);
MiniportAllocateComplete runs at IRQL = DISPATCH_LEVEL.
CoCreateVcHandler
Ignore.
CoDeleteVcHandler
Ignore.
CoActivateVcHandler
Ignore.
CoDeactivateVcHandler
Ignore.
CoSendPacketsHandler
Ignore.
CoRequestHandler
Ignore.
CancelSendPacketsHandler
取消某個特定群組的封包傳送。
作法如下:
PnPEventNotifyHandler
- 在處理 surprise removal 部份,所有在 Windows XP 版本之後,不會直接 access 硬體的 miniport driver,都應該在呼叫 NdisMSetAttributesEx() 的時候,傳入 NDIS_ATTRIBUTE_SURPRISE_REMOVE_OK 這個 flag。如果有設定這個 flag 的話,當使用者突然移除 NIC 時,就不會顯示出警告訊息。Driver 應該 completes pending Ndis Packets。 除此之外,在 MiniportPnPEventNotify() 之外的任何動作,都應該確認卡是否已經被移除(可以透過 register write/read 的方式,確認 register 是否可以被寫入)。如果不做這個確認的話,Driver 有可能會一直嘗試讀取已被移除的 NIC 的 register,這樣會導致 NDIS 不會呼叫到 MiniportPnPEventNotify() 。
- 在處理 power source 部份,miniport 可以利用 MiniportPnPEventNotify() 傳入的參數,調整 NIC 的 power comsuption。例如,如果收到 NdisPowerProfileBattery 的話,就調低 power comsuption。,如果收到 NdisPowerProfileAcOnline 的話,便調高 power comsuption。
VOID MiniportPnPEventNotify(
IN NDIS_HANDLE MiniportAdapterContext,
IN NDIS_DEVICE_PNP_EVENT PnPEvent,
IN PVOID InformationBuffer,
IN ULONG InformationBufferLength,
);
System support for MiniportPnPEventNotify is available in Windows XP and later operating systems.
MiniportPnPEventNotify runs synchronously at IRQL = PASSIVE_LEVEL and in the context of a system thread.
AdapterShutdownHandler
當系統關機的時候(不管是使用者選擇或是系統發生無法回覆的錯誤),MiniportShutdown() 會把 NIC 重置成初始的狀態。每個 Miniport Driver 都必須提供 MiniportShutdown(),不管是透過 NdisMRegisterAdapterShutdownHandler() (~NDIS5.0) 還是 NdisMRegisterMiniport() (NDIS5.1~)。
MiniportShutdown() 不用釋放任何配置過的資源,它只是 Stop the NIC。
VOID MiniportShutdown(
IN PVOID ShutdownContext
);
MiniportShutdown should call no NdisXxx functions.