wince下DM9000A网卡驱动移植及学习总结---4

来源:互联网 发布:linux查看某个进程 编辑:程序博客网 时间:2024/04/29 15:50

6.      CQueue(队列)类分析

CQueue类紧密相关的是队列header结构体:PCQUEUE_GEN_HEADER,原型如下:

typedefstruct      _CQUEUE_GEN_HEADER

{

    struct      _CQUEUE_GEN_HEADER *pNext;

    U32        uFlags;

    PVOID   pPacket;

    U16        nReserved;

    U16        nLength;

} CQUEUE_GEN_HEADER, *PCQUEUE_GEN_HEADER;

这个类有两个私有的成员变量,定义如下:

CQUEUE_GEN_HEADER  *m_pHead,*m_pTail;

这个类完成的是将上面的结构体变量XXX虚拟的放入一个FIFO,先入先出的一个队列中,m_pTailFIFO的尾指针,m_pHeadFIFO的头指针。

入队函数Enqueue完成的功能是入队,每有一个XXX进入队列,让上一个入队的XXXpNext指向当前入队的这个XXX,表示它的下一个XXX是新进来的这个XXX,最后将FIFO的尾指针修改,这样建立这个虚拟FIFO的链接关系。出队函数Dequeue相反,将FIFO的头指针输出,然后修改头指针。(XXX意为FIFO中存放的一个结构体变量)

GetHead函数是获得FIFO的头指针,IsQueueEmpty函数是判断FIFO是否为空,Size函数返回FIFOXXX的个数。

 

7.      CSpinlock类分析:

CSpinlock类构造函数调用NDIS库函数NdisAllocateSpinLock,初始化一个NDIS_SPIN_LOCK类型的变量,目的:为了同步访问驱动中的共享资源而不涉及中断。

类成员函数Lock调用NDIS库函数NdisAcquireSpinLock,完成的功能是spin lock,这样调用者可以访问共享资源。

类成员函数ReleaseNDIS库函数NdisReleaseSpinLock,完成的功能与上面相反,释放lock

 

 

 

 

8.      CMutex类分析:

构造函数调用内核函数InitializeCriticalSection,初始化一个临界对象,Lock函数调内核函数EnterCriticalSection进入临界状态,TryLock函数调用内核TryEnterCriticalSection也是进入临界状态但是会很块返回而不管是否进入时,Release函数调内核函数LeaveCriticalSection离开临界状态。

 

9.      C_Exception

这个类是调NDIS库打印一些调制信息。

 

10.      网卡send分析

NDIS驱动会调用MiniportSend函数进行发数操作,而MiniportSend函数将调用函数DriverSend函数。MiniportSend函数原型如下:

NDIS_STATUS MiniportSend(NDIS_HANDLE MiniportAdapterContext, PNDIS_PACKET Packet, UINT Flags)

  参数说明:

u  MiniportAdapterContext:一个指向网卡结构的句柄,该网卡结构在MiniportInitialize函数中被创建。

u  Packet:指向一个包描述符,该包描述符用于描述被发送的数据包

u  Flags:发送标记

注:后面两个参数通过DriverSend函数继续传递。

下面我们详细看看发送。

首先我们需要注意在几个hpp文件中定义的几个CQueue类成员变量:

l  Driver类中定义的CQueue类成员变量:m_TQueue

l  deivce类中定义的CQueue类成员变量:m_TQStandbym_TQWaiting

l  Dm9isa类中定义的CQueue类成员变量:m_RQueuem_RQStandby

Dm9isa类中的m_RQueuem_RQStandby是在接收时使用,后面再讲,我们先看与send相关的前3个。

先来回顾以下EDriverInitialize中的如下语句:

/* init tx buffers */

U32        m,uaddr;

if(!(uaddr = (U32)malloc(sizeof(DATA_BLOCK)*            (m=m_pLower->m_szConfigures[CID_TXBUFFER_NUMBER]*2)))) //m=32(0x20)*2=64

       THROW((ERR_STRING("Insufficient memory")));

for(;m--;uaddr+=sizeof(DATA_BLOCK))

       m_TQueue.Enqueue((PCQUEUE_GEN_HEADER)uaddr);

这是初始化tx buffers,读注册表认为上述m = 64,每一个DATA_BLOCK是由header+buffer[0x5f0]组成,共malloc64个这样的DATA_BLOCK,之后的for是所有的DATA_BLOCKheaderm_TQueue类实例的入队处理。

注意:入队出队的FIFO中存的都是header而不是整个DATA_BLOCK

DATA_BLOCK的结构如下图:

接下来我们看函数DriverSend

这个函数首先执行的代码如下:

if(!(pobj = m_TQueue.Dequeue())){….}

m_TQueue类实例的出队操作,返回值是FIFO中的一个XXX,由于m_TQueue类实例在EDriverInitialize时已经初始化过了,所以这里肯定存在,if判断语句条件不成立,所以不进入。继续DriverSend函数:

PU8       pcurr = (PU8)CQueueGetUserPointer(pobj);

CQueueGetUserPointer函数原型如下:

#define    CQueueGetUserPointer(ptr)   \

((PVOID)((U32)(ptr) + sizeof(CQUEUE_GEN_HEADER)))

所以其实CQueueGetUserPointer函数完成的功能就是将指针指向DATA_BLOCKbuffer[]

继续看DriverSend函数:

NdisQueryPacket(pPacket,  &uPhysicalBufferCount,  &uBufferCount,

      &pndisFirstBuffer,  &uTotalPacketLength);

NdisQueryPacketNDIS库函数,这里用于获得MiniportSend传入的参数Packet的信息,接下来的一个

for()

{…}

作用是将Packet的内容拷贝到DATA_BLOCKbuffer中。最后几行代码如下:

pobj->pPacket = (PVOID)pPacket;

pobj->uFlags = uFlags;

pobj->nLength = uTotalPacketLength; 

m_pLower->DeviceSend(pobj);

是将Packet的信息存到DATA_BLOCKheader里面,然后将这个header做为参数调用函数DeviceSend

总的来说,DriverSend就是对MiniportSend传入的参数Packet的信息进行分析,然后重新拷贝到DATA_BLOCK中,最后调用DeviceSend函数完成真正的功能。

下面我们看函数DeviceSend。这个函数在子类中实现,首先执行代码如下:

if(pObject) m_TQStandby.Enqueue(pObject);

即将传入的headerm_TQStandby类实例的FIFOm_TQStandby没有初始化,所以FIFO中只有这一个header,接下来执行:

               m_nTxPendings++;

这个是等待发送的包的计数,发送完成后执行m_nTxPendings--操作。接下来执行:

m_TQWaiting.Enqueue(pcurr=m_TQStandby.Dequeue());

       m_TQStandby出队,而m_TQWaiting入队,所以这里m_TQStandby类实例其实是个缓冲的作用,可以认为是不存在的,header直接传到m_TQWaiting类实例。接下来执行如下:

      /* fill data */

      DeviceWriteString((PU8)CQueueGetUserPointer(pcurr),pcurr->nLength);

      

      DeviceWritePort(DM9_TXLENH,HIGH_BYTE(pcurr->nLength));

      DeviceWritePort(DM9_TXLENL,LOW_BYTE(pcurr->nLength));

// TXCR<0>, issue TX request

DeviceWritePort(DM9_TXCR, MAKE_MASK(0));

       这几句是往DM9000A的发数缓冲写入要发数的数据,往发数的长度寄存器写入这些数据的长度,最后往DM9000A的发送控制寄存器(TCR)写发送请求。

       发送完成后,会产生一个中断,执行前面说讲过的中断操作,判断是包1还是包2发完成,最后执行:

DeviceSendCompleted(m_TQWaiting.Dequeue());

m_TQWaiting的出队操作(完成后m_TQWaiting将继续保持为空,等待下一个send,然后从m_TQStandby出队时获得一个header将其入队,如此反复…),和m_TQueue的入队操作(send最开始时执行出队,其实这个FIFO中保存的header只是保证在内存中开辟了64*sizeof(DATA_BLOCK)大小的区域,出队后对出队的header填入相关信息,进行发送,结束后重新进行入队操作,保证了FIFOsize,而内存中存的数据已经没有意义,下一次使用这个header(FIFO转一圈后这个header再次出队时)会覆盖现在的内容,所以这里只是一个简单的入队操作。

       上述就是整个网卡发数的操作,总的来说,就是:上层会将发数的数据放在packed参数里面,然后驱动将分析这个Packet,最后将其送到DM9000ATX SRAM,将长度也放入相应寄存器,最后执行发送请求。不过复杂的是里面有几级缓冲,将上层给的数据一级一级的放到缓冲里,最后发数。上面“6”中所讲的CQueue类其实目的就是将这些缓冲做成一个虚拟的FIFO,先入先出,保持顺序。

 

11.      结束。

这段时间一直学习网卡,所以做了详细的总结,以供自己日后回顾复习和他人学习。

原创粉丝点击