接上次继续:
3. 初始化完成后,网卡将可以收发数据了。最先执行的是MiniportQueryInformation函数:
NDIS_STATUS MiniportQueryInformation( NDIS_HANDLE MiniportAdapterContext, NDIS_OID Oid, PVOID InformationBuffer, ULONG InformationBufferLength, PULONG BytesWritten, PULONG BytesNeeded)
该函数为网卡信息查询函数,返回网卡状态等信息。
MiniportAdapterContext:一个指向网卡结构的句柄,该网卡结构在MiniportInitialize函数中被创建。
Oid:网卡的OID信息码。
InformationBuffer:指向一个网卡信息Buffer,用于存放查询结构
InformationBufferLength:网卡信息Buffer的大小
BytesWritten:实际写入网卡信息buffer的字节数
BytesNeeded:如果网卡信息buffer不够大,返回还需要多大buffer长度
NDIS库会先调用这个函数来判断目前网卡的信息状态(这个函数调用Driver类的函数:DriverQueryInformation,Device类的DeviceQueryInformation函数没有实现),完成的功能是把m_szCurrentSettings[]中的相应信息拷贝到InfoBuffer中去
根据输入的不同的Oid拷贝不同的信息,系统会多次调用来查看信息。
4. 接着会调用函数:MiniportSetInformation,这个函数参数大致同MiniportQueryInformation (参数Oid,InfoBuffer等同DriverQueryInformation函数)。
此函数主要完成功能:根据不同的Oid设置不同的m_pLower中成员变量的信息,实际上,主要完成2个Oid的值:
1) 针对Oid=OID_GEN_CURRENT_PACKET_FILTER:
调用函数m_pLower->DeviceOnSetupFilter(0x0b),0x0b这个值是通过InfoBuffer得到的。完成这步后,在DeviceOnSetupFilter函数中会将
m_szCurrentSettings[SID_GEN_CURRENT_PACKET_FILTER]设置成0x0b
2) 针对Oid==OID_802_3_MULTICAST_LIST,设置:
1) m_pLower->m_szMulticastList[][]信息
2) m_pLower->m_nMulticasts
3) 调用函数m_pLower->DeviceOnSetupFilter(0x0b | 0x02),所以带入的参数还是0x0b.
5. 网卡中断及收数详解:
上面提到在初始化时中会调用EDeviceRegisterInterrupt函数,而这个函数会调用NDIS库函数NdisMRegisterInterrupt来告诉上层中断信息,注意这个库函数的第三个参数UINT InterruptVector是中断向量号,在这里就是我们网卡中断所对应的逻辑中断号(前面说过这个中断号是从platform.reg中读得的)。这个库函数完成的功能就是告诉NDIS库我们所使用的中断号,推测NDIS库将会InterruptInitialize函数来使能初始化中断,而InterruptInitialize函数会调用函数OALIntrEnableIrqs。
注册了中断后,NDIS库会直接调用MiniportISRHandler函数和MiniportHandleInterrupt函数处理相关中断。下面分析这两个函数:
VOID MiniportISRHandler ( PBOOLEAN InterruptRecognized, PBOOLEAN
QueueMiniportHandleInterrupt, NDIS_HANDLE MiniportAdapterContext)
该函数为网卡的ISR函数,该函数中应做尽可能少的工作,大部分工作应该交给MiniportHandleInterrupt函数来完成。
InterruptRecognized:中断确认。如果确实是一个网卡中断,返回TURE。
QueueMiniportHandleInterrupt:如果需要MiniportHandleInterrupt函数处理,返回TRUE。
MiniportAdapterContext:一个指向网卡结构的句柄,该网卡结构在MiniportInitialize函数中被创建。
该函数调用函数DriverIsr,我们看DriverIsr函数:
DriverIsr函数首先调函数DeviceDisableInterrupt来关闭DM9000A内部中断的产生,让其不再发中断给CPU,然后调函数DeviceGetInterruptStatus来获得DM9000A中断状态寄存器的值并保存起来(其实后面保存到成员变量m_uRecentInterruptStatus里了),然后调用函数DeviceSetInterruptStatus来清除中断标识(注:DM9000A的中断状态寄存器是通过写“1”来清除的),然后将输出的两个参数:中断确认设置为“1”和需要MiniportHandleInterrupt函数处理设置为“1”,最后调用函数DeviceIsr来再处理,实际上这里没有任何处理。
MiniportISRHandler函数分析到此位置,总的来说完成的功能就是关闭DM9000A内部中断,使其别再给CPU中断信号了,然后读中断状态寄存器的值并保存,然后清除DM9000A中断状态寄存器的中断状态。
上述MiniportISRHandler函数完成后,将进入MiniportInterruptHandler函数对中断做真正的处理,按照前面读得的中断状态寄存器的值判断是接受中断还是发送结束中断,分别做相应处理,下面我们仔细看MiniportInterruptHandler函数。
VOID MiniportInterruptHandler (NDIS_HANDLE MiniportAdapterContext)
该函数为网卡的中断处理函数。MiniportAdapterContext:一个指向网卡结构的句柄,该网卡结构在MiniportInitialize函数中被创建。
这个函数主要调用函数DriverInterruptHandler函数来完成功能的,而DriverInterruptHandler函数主要调DeviceInterruptEventHandler函数完成真正中断处理的(这个函数带入的参数即上面保存的DM9000A内部中断状态寄存器的值),最后调函数DeviceEnableInterrupt使能DM9000A内部中断的产生。所以我们主要看Device类的函数DeviceInterruptEventHandler,真正实现功能的是在子类中,代码如下:
if(uValue & 0x01) Dm9LookupRxBuffers();//收数处理
// return if not TX latch
if(!(uValue & 0x02)) return;//溢出或者收数不再处理直接返回
//以下是发数处理,通过读DM9_NSR,判断是否发完成
U32 nsr;
nsr = DeviceReadPort(DM9_NSR);
// check TX-END2 数据包2发送完成
if(nsr & 0x08)
{
m_nTxPendings--;
DeviceSendCompleted(m_TQWaiting.Dequeue());
}
// check TX-END1 数据包1发送完成
if(nsr & 0x04)
{
m_nTxPendings--;
DeviceSendCompleted(m_TQWaiting.Dequeue());
}
// report tx available now 如果任何一包发送完成,则通知NDIS可以再发送
if( nsr & 0x0C )
NdisMSendResourcesAvailable(m_pUpper->GetNdisHandle());
注:原程序中没有上面代码中的汉语注释,注释是我加的,方便理解代码意思。
通过上面代码可以发发现,DeviceInterruptEventHandler中断处理函数其实完成的功能是判断得到的中断是发送中断还是接受中断,如果是发送中断,则读DM9_NSR寄存器,判断是数据包1还是2发送完成了,然后通知NDIS库可以再发数。如果是收数中断,调Dm9LookupRxBuffers函数,完成收数处理。
发数时较为简单,就是判断是包1还是包2发送完了,然后调DeviceSendCompleted函数通知发送完成,具体处理后面我们再看,我们先看收数Dm9LookupRxBuffers函数处理:
首先我们看DM9000A数据手册9.4小节Packet Reception中描述,RX SRAM是个环形数据结构,软硬件复位后 RX SRAM起始地址是0x0c00。每一包数据有4个byte的包头(header),之后是收到的数据包括CRC校验。4个byte的header是01h, status, 字节数低8位,字节数高8位。
知道上述原理后,我们后面就会好理解。对应上面所说的header,程序中有个结构体PDM9_RX_DESCRIPTOR,定义如下:
typedef struct {
U8 bState;
U8 bStatus;
U16 nLength;
} DM9_RX_DESCRIPTOR, *PDM9_RX_DESCRIPTOR;
第一个字节就是0x01,第二个是status,第三个是接收字节长度。
我们看Dm9LookupRxBuffers函数,下面这个循环完成的就是一包一包的收数:
for(pdesc=(PDM9_RX_DESCRIPTOR)&desc;;)
{
….
desc = DeviceReadDataWithoutIncrement();
if(pdesc->bState != 0x01) break;
desc = DeviceReadData();
….
if((pdesc->nLength > DRIVER_BUFFER_SIZE))
{
DeviceIndication(AID_LARGE_INCOME_PACKET);
break;
}
DeviceReadString((PU8)&szbuffer,pdesc->nLength);
if(pdesc->bStatus & MAKE_MASK4(3,2,1,0))
{
errors++;
continue;
}
DeviceReceiveIndication(0, (PVOID)&szbuffer, pdesc->nLength);
}
DeviceReadDataWithoutIncrement是从RX SRAM中读4个byte的数,不过RX SRAM读指针不变,这里意思就是读出上述的header,然后判断第一个字节是否是0x01h,接下来调DeviceReadData函数,完成的功能还是从RX SRAM中读数,不过这次读完后读指针按照读的长度及工作模式的设置改变,后面的DeviceReadString函数也是一样,DeviceReadString函数是读包中实际的有效数据。这里RX SRAM读指针增加不增加的用途如下:每一包有一个header,首先不增加即读指针保持不变,读得header后判断是否是正确的header,不正确直接丢弃,下一包继续从这个读指针的地址读数,正确的话(地址指针按照一定规则增加)从header开始读数,包括后面的读实际有效数据。
注意上述代码中有两个if判读,第一个if是包的长度是否大于接收buffer的最大长度0x5f0 = 1520,如果大于,调用Drive类函数DriverIndication(将Drive类成员变量m_bSystemHang设置为1),然后跳出for循环。第二个if是判断header的第二个字节是否有错误标志,有错误的话errors++,跳出本次循环,开始读下一包数据。
for循环最后一句是调用Drive类成员函数DriverReceiveIndication,完成的功能是将收到的数据告知NDIS库。
for循环后代码如下:
REPORT(TID_GEN_RCV_OK, counts);
REPORT(TID_GEN_RCV_ERROR, errors);
这两句是将这次中断收到的数据包数和错误次数存入Device类成员变量数组m_szStatistics[]相应标号中。
以上就是Dm9LookupRxBuffers函数主要功能的分析。