Monday, December 03, 2007

NDIS Driver[2]

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

取消某個特定群組的封包傳送。 作法如下:
  • 在儲存 NDIS_PACKET 的 software queue 內搜尋,利用 NDIS_GET_PACKET_CANCEL_ID() 一個一個檢視封包的 ID 是否跟 CancelId 相同,如果相同的話,Driver 必須把這些 NDIS_PACKET 由 software queue 內移除,並呼叫 NdisMSendComplete()、NdisMSendWanComplete() 或 NdisMCoSendComplete(),並且把 Status 設為 NDIS_STATUS_ABORTED。


  • VOID MiniportCancelSendPackets(
        IN NDIS_HANDLE  MiniportAdapterContext,
        IN PVOID CancelId
    );
    

    System support for MiniportCancelSendPackets is available in Windows XP and later operating systems.
    MiniportCancelSendPackets runs at IRQL <= DISPATCH_LEVEL.

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.

No comments:

codeblock