USBRNDIS.docx
- 文档编号:15383041
- 上传时间:2023-07-04
- 格式:DOCX
- 页数:15
- 大小:27.42KB
USBRNDIS.docx
《USBRNDIS.docx》由会员分享,可在线阅读,更多相关《USBRNDIS.docx(15页珍藏版)》请在冰点文库上搜索。
USBRNDIS
USB-RNDIS
RNDIS原理分析--PDD部分
---------------bynasiry
转载请说明出处,并通知我
1.初始化
首先我们从PDD_Init开始。
所谓初始化的动作通常包含对硬件设备的初始化,一般说来就是通过设置设备寄存器来对设备进行必要的配置,另外一个方面就是软件的初始化,包括全局数据变量的空间申请初始化,中断函数的挂接,其他函数指针的挂接。
既然需要读写设备的状态寄存器设备的端口/地址就是必不可少的,首先PDD_init先检查有没有从MDD传过来的设备地址,如果没有,通过PciFind2890找一下我们需要的2980有没有在PCI以查卡的形式在某一个插槽上,如果继没有由MDD传递过来的设备地址,PCI插槽上也没有我们需要的2890,怎么办?
还能怎么办,既然没有的话,我也干不下去了,returnFalse算了,88下次再见:
P
如果在PCI插口上发现2890的话,就比较麻烦一点,因为需要去计算PCI总线上的设备偏移才能得到设备地址,不仅如次还要去摆弄一个叫9054的片子来符合和PCI的规范,其实我们要的只是一个地址和一个中断源,没有必要再PCI设备上搞来搞去,最简单的办法就是假设这个2890是直接挂在系统总线上的,事实上现在用得很普遍的ARM体系下通常都是没有PCI总线的,这样子我们就只用分析MDD直接传递了设备地址的情况,简单还不乏实用性。
总之,我就是不分析PCI总线相关的部分,因为我不会。
下面废话少说进入正题。
PDD_init有两个参数第一个是一个结构指针原形如下:
typedef struct_RNDIS_PDD_CHARACTERISTICS
{ PFN_PDD_SEND_RNDIS_MESSAGE SendRndisMessageHandler;
PFN_PDD_SEND_RNDIS_PACKET SendRndisPacketHandler;
PFN_PDD_INDICATE_RNDIS_PACKET_COMPLETEIndicateRndisPacketCompleteHandler;
PFN_PDD_SET SetHandler;
PFN_PDD_GET GetHandler;
PFN_PDD_ISR ISRHandler;
DWORD dwIRQ;
DWORD dwMaxRx;
DWORD dwBaseAddr;
// BUSspecificinformation
// IfitisPCIthenthisdriverwillbeloadedbyPCIenumeratorthrough
// NDIS.
BOOL bPCIDevice;
// TheseareonlyneededforPCItypedeviceANDifthedriverchooses
// touseGIISR.DLLasdefaultISR.
BOOL CheckPort; //Iftrue,checkporttoseeifdeviceisassertingIRQ
BOOL PortIsIO; //PortisIOport(possiblytrueonlyforx86)
BOOL UseMaskReg; //Iftrue,readfromMaskAddrtoobtainmask
DWORD PortAddr; //PortAddress
DWORD PortSize; //Portdatawidthinbytes
DWORD Mask; //MasktouseondataporttodetermineifdeviceisassertingIRQ
DWORD MaskAddr; //Addressofregistertouseasmask
} RNDIS_PDD_CHARACTERISTICS,*PRNDIS_PDD_CHARACTERISTICS;
这个结构包含了一系列的函数指针和地址,中断,和最大接收数等信息,没有提到的这些个结构成员都是PCI设备所需的,这里我们仅仅需要把bPCIDevice设置为False也就是说我们并没有使用PCI设备就可以了。
注意这个参数的类型是OUT,MDD也就是通过这里得到设备的信息的。
因此在这里需要填充该结构,然后完成对硬件的初始化。
另外还有一个相当重要的结构,这个结构在全局范围内存在一个实例,是我们的PDD工作的中心,我们来看看这个结构的原形。
typedef struct
{ volatilePUCHAR pucBaseAddress;
volatilePUCHAR puc9054Address;
ULONG ulIRQ;
DWORD dwSysIntr;
BOOL bConnected;
BYTE bUsbAddress;
// EP0relatedoperations..
BOOL bSending;
LIST_ENTRY listTxRndisMessageQueue;
PDATA_WRAPPER pCurrentSendRndisMessage;
PBYTE pbCurrentSend;
DWORD dwCurrentSendSizeLeft;
UCHAR ucScratchBuffer;
BYTE pbEP0ReceiveBuffer[EP0_MAX_RECEIVE_BUFFER];
DATA_WRAPPER EP0DataWrapper;
DWORD dwExpectedRxSize;
DWORD dwTotalReceived;
PBYTE pbCurrentRx;
// EP1relatedoperations..
PDATA_WRAPPER pEP1DataWrapper;
// EP2relatedoperations..
PDATA_WRAPPER pEP2DataWrapper;
PBYTE pbEP2CurrentSend;
DWORD dwEP2TotalSent;
BOOL bEP2ShortPacketSent;
UCHAR MacAddress[6];
}RNDIS_2890;
pucBaseAddress是用于存放设备的基地址,puc9054Address仅仅是在使用PCI卡的时候使用,原因上面说过了。
ulIRQ指硬件中断号,dwSysIntr
也是在PCI模式下用,因为这种模式下我们只能使用"可安装"的中断(因为PCI查卡位置不同中断不同)。
bConnected是连接状态的标示,bUsbAddress则是来自于USB协议本身的要求。
硬件的初始化是在NET2890Init中完成的,进行的是一系列的寄存器操作以完成对USB各个ENDPoint的配置,如果对源码或者这个芯片本身有兴趣的话可以参考NET2890USBInterfaceControllerForRevision2IC来看具体的代码,我这里仅仅将相应的配置总结出来如下:
RNDIS2890配置
ENDPOINT
EP0 control(IN/OUT) enable
1.FIFOValidModeFIFOFlush
2.EP0PacketSize=8byte
3.Handshakereceive
EP1(EPA)OUTenable
BULKmdoe
Maxpacketsize=64byte
handshakereceive
EP2(EPB)INenable
BULKmode
Maxpacketsize=64byte
FIFOvalidandFIFOFlush
EP3(EPC)INenable
INTERRUPTMODE
MaxPacketsize=sizeof(INTERRUPTDATA)
FIFOvalidandFIFOFlush
从这些配置信息可以看到,在RNDIS的实现中使用了4个ENDPOINT分别创建4个管道在PC与设备之间传输数据和传输控制信号。
其中EP0用于USB协议本身的控制,EP1作为Host-Device的上行传输通道,EP2做Device-Host的下行传输通道,这里的上行下行都是相对主机而言的。
最后有一个比较特别的就是EP3,这个Endpoint的在这里的作用是用于产生中断,具体的做法就是将该端点的FIFO溢出值等于中断结构体的大小,这样每次得到一个中断数据就马上产生一个中断,结合起来就有了一条具备完整双工通路和控制通路的完整链路用于模拟网卡了。
以下是上面提到的中断结构。
typedefstruct_INTERRUPT_DATA
{ DWORD Notification;
DWORD dwReserved;
} INTERRUPT_DATA,*PINTERRUPT_DATA;
设置完了这些端点以后,首先在开启中断之前清除相关的中断标示,以免产生不必要的误动作,这里主要是SOF(Startofframe),SetupPacketInterrupt,rootportresetInterruptstatus,InputPinChangeInterruptStatus几个状态寄存器。
确定了这些中断不会被误触发之后就开始对中断寄存器进行配置了。
下面照例将这些配置给写下来,以便分析。
EP0
DataINTokenInterruptenable
DataPacketReceivedInterruptEnable
DataPacketTransmittedInterruptEnable
EP1(EPA)
DataPacketReceivedInterruptEnable
EP2(EPB)
DataPacketTransmittedInterruptEnable
然后打开SetupPacketInterrupt和RootportresetInterrupt用于EP0接收并响应复位和配置。
USB的硬件初始化的动作到现在就完成了,所以置寄存器位Deviceconfigured,告知2890芯片我们已经准备好了,它可以工作了。
然后模拟一次断开连接的动作,等待3ms确认2890可以工作了以后,去掉断开模拟连接。
这个动作目的是复位硬件和软件环境。
下面这个USB设备端口就可以正式工作了。
再回到PDD_Init的过程中来,完成了对硬件的初始化余下的工作自然是软件部分的事情,首先初始化用于EP3的InterruptData实例。
再来就是初始化我们将来返回给MDD的RNDIS_PDD_CHARACTERISTICS结构,分别挂接上PDD_SendRndisMessage,PDD_SendRndisPacket,PDD_Set,PDD_Get,PDD_ISR,PDD_IndicateRndisPacketComplete这几个函数以供MDD调用,以及中断,最大的传输packet大小,设备基地址等信息。
至于PCI相关的部分,设置了pRndisPddCharacteristics->bPCIDevice=False就可以不管了。
然后通过调用InitializeListHead初始化以后将会用到的消息对列后初始化的工作也就完成了。
在后面的工作就不是顺序的了,依靠的是中断来驱动的.
RNDIS源码分析--PDD部分
2.中断服务程序
事实上大部分RNDIS代码都是由中断驱动的,由于USB和网卡本身控制起来就是比较复杂的,直接导致了这个ISR庞大的体积。
我们先来总览一下这个中断服务程序的概貌,首先使用一个do{}while;来将所有的服务代码全部装进去,然后从上到下每一个单独的中断服务子程序使用一个if语句来检查相应的中断,如果该中断被执行则清除该中断并继续进行循环检查下一个中断,直道所有的中断都得到响应后才退出该中断服务。
这样一来这些中断服务子程序之间就有了优先级的排布,从上到下看,优先级最高的在最上方,最低的在程序的最末。
同时这样也就允许了在发生多个中断时,这些中断都将会按我们的意图以优先级的由高到低的顺序得以执行。
好了我们来看看实际的代码吧。
每次循环之前都需要读取2890的两个中断状态寄存器,并将结果与相应条件相比较并执行中段服务代码,有相关寄存器的操作我将仅仅描述这些中断信号的名称和作用。
1.InputPinChangeInterruptStatus|SuspendRequestInterruptStatus
前一个状态指的是检查到USB电源状态的改变,对应的动作也就使插拔USB线,第二个状态是受到休眠请求时发出的。
这是最紧急的事情,关系到所有其他的代码还要不要运行(线都给拔了难道还能玩模拟802.11?
),所以放在最前面。
具体的代码执行以下内容,首先:
清除中断。
然后通过调用MddDisconnect()做软件上断开连接的准备。
2.RootPortResetInterruptStatus
这个信号对应的是USB根集线器复位的动作,也就是主机复位。
实际代码照管理清除中断,冲刷EP0的FIFO,同时无效所有当前的发送动作,这个时候主机已经复位,所以通过调用MddSendRndisMessageComplete强制结束当前的发送数据的动作,和当前还在等待接收的数据包然后通知MDD断开连接。
等待重新初始化和配置USB,这里所说的初始化和前面我们上一部分的初始化不同,这里仅仅是将USB的逻辑状态恢复到最先的时候。
3.EndPoint0InterruptStatus|SetupPacketInterrupt
这些代码中很大部分都是USBSpecificationsv.1.1的内容,最好对照参看.这个中断服务子程序对应EP0的传输和接收到主机发来的配置信息。
EP0又对应了很多的子处理,同样使用了一个大的循环结构来检查和执行USB的各种控制动作。
不同的是这次检查的是IRQStatus2和EP0IRQstatus两个寄存器.下面我们仍旧按照检查的先后顺序(优先级由高到低的顺序)来解读这些处理代码。
a.DataPacketTransmittedInterrupt.
这个中断发生在EP0完成发送数据到Host的动作时发生,也就是说检查EP0现在是否忙,如果在就绪状态下的话就设置USB协议所需的USB地址。
b.SetupPacketInterrupt.
该中断在USB接收到设置数据包时发生。
在该状态下又有两个子状态
b1.DataINTokenInterrupt
当接收到从主机发来的DataIN标示时产生,这个时候需要调用EP0Send发送一组确认数据完成接收的握手动作,这样主机接下来就可以将数据发送过来了。
(tobecontinue)
b2.DataPacketReceivedInterrupt
由于握手动作以上面已经动作,接收从主机发送过的数据来时该中断产生,故调用完成EP0Receive动作并读入来自主机的数据.B段所示的代码事实上也是通常的接受动作,但需要的注意的是这些代码仅仅在接受请求为设置数据包时动作。
完成了设置数据的读入以后,剩下的所有发送/接收请求
,所以我们需要清除这些非设置数据请求的接收/发送请求。
这个动作同样是通过写寄存器的操作完成.剩下的事情就是完成解析并执行SetupPacket的内容了。
由于USB协议约定的多数设置都是在这儿完成的所以,导致下面会有很多分支,而每个SetupPacket仅仅包含一个设置信息,也就是完成一项设置之后必须重新读入新的SetupPacket,所以这里的分支并不使用单独的dowhile结构来实现,取而代之的是两个个switch语句。
一个个switch的执行分支的依据是RNDIS定义的请求。
另外一个是standrequest的类型,我们照例将这些request例下来分别讨论。
其中前两个是Class或者Vendorrequest的分支,后面的则是standrequest的分支。
而这个switch语句仅仅处理了以下两个request的类型是Remote-NDIS定义的,这两个请求的属于类驱动或是自定义的请求类型,因此其他所有的请求在这里都将被忽略。
在这里顺便简单说明一下USB设备加载的过程,这会有利于了解以下内容,事实上USB设备的加载(这里并不是专门说明USB协议的,简单起见仅仅说正常加载的过程)可以分为5个过程:
首先是attach,也就是设备连接到主机的过程,之后很自然的就是powered也就是上电,这两布虽然在USB协议当中相当重要但是和我们的程序代码关系事实上并不很大,之后将会进入一个叫做default的默认状态,在这个状态下设备不会对总线上的任何请求作出响应,直到接收到一个复位信号后设备进入配置地址状态(address),设备复位后会使用一个默认的地址作为设备地址,这个时候将通过默认的EP0建立管道完成Set_address的动作,这样设备就得到一个唯一的设备地址,就可以正式参加条总线上的通讯了,这里说正式仅仅是相对的,这个时候的设备仅仅是建立了最基本的配置物理链路,并不具备真正的数据通讯能力,所以我们还需要进一步配置以达到使用该USB设备通讯/传输数据/控制设备的能力,所以我们需要将设备的类型,名称,使用的驱动类ENDPoint的配置等信息提交给主机后,主机加载上正确的设备驱动程序这样在主机和设备的协同下才能达到我们使USB正常工作的目的,达到了这一步也就就是我们所需要的Configured状态,只有在这个状态下我们才能正常地进行传输。
另外还有一点需要说明,在RNDIS协议里面USB设备使用的是CDC类,也就是说在这些设置需要遵循三个协议:
USB,USBCDC,MicrosoftRNDIS。
好了准备工作已经做好我们就来简要的看看下面的具体代码的内容吧。
下面两个请求的是USBCDCAbstractControlModel所必需的。
事实上这是RNDIS所使用的仅有的两个AbstractControlMode请求。
由于采用了CDCAbstractcontrolModel,所以RNDIS要求最少需要两个端点来构造两个管道,一个用于实现管理任务(management找不到合适的中文来描述这个单词),另外一个管道用于实现所需的消息(或者说通告notification),管理元素都可以用请求的形式来实现,所以我们就可以直接使用EP0来实现,而另外一个需求则是来之消息,消息需要像中断一样来通告设备,并让其响应,因此我们使用另外一个专门的Interrupt型端点EEP3来实现。
这两部分的实现我们在后面再讨论。
i>SEND_ENCAPSULATED_COMMAND
类型为hosttodeive类驱动,接口型请求(见RNDIS和USBspecification).
ii>GET_ENCAPSULATED_RESPONSE
类型为Device-to-host类驱动接口型(见RNDIS和USBspecification)。
由于使用了USB网络通讯类设备规范,顺其自然地也就使用以上两个类请求。
分别为收/发数据包的请求。
主要用来传递与网络类设备相关的信息和控制信息。
这些收到/发送的命令都要通过MDD来进行分发处理。
这个我们以后再进一步介绍。
iii>GET_STATUS
请求类型为devicetohostendpoint作用为获得EP0,EP1,EP2三个ENDPoint的状态,并调用EP0PrepareSend将其送至主机。
iv>CLEAR_FEATURE
清除EP0EP1的stall状态。
V>SET_FEATURE
设置EP0EP1的stall状态
以上三个请求类型为EndPoint型,因此仅仅是endpoint的操作。
vi>SET_ADDRESS
设置USBDevice的USB地址,事实上仅仅是将这个地址送交gRndis2890.bUsbAddress存放,让后让3-a的步骤设置该地址。
vii>GET_DESCRIPTOR
向主机提交各描述符的内容,同样是通过EP0PrepareSend完成的。
这些描述符是之前已经定义好了的,具体的设置我列在下面给大家参考。
首先是描述设备类型和型号的DeviceDescriptor,包含了设备名称,类别,生产厂家等等信息,通过得到的这些信息Host会去找到合适的设备驱动程序在主机上加载,与我们的这部分代码配合运行。
注意说明了是RNDIS的描述项部分是RNDIS协议指定了的,不能随便修改否则不能保持和rNDIS协议的兼容。
Device Descriptor value note
bLength 18 lengthoftheDescriptor
DescriptorType 0x1 Device
bcdUSB 0x1,0x1 USBv1.1
bDeviceClass 0x02 CommunicationDevicedefinedbyR-NDISspecification*
bDeviceSubClass 0x0 RNIDSspecificationdefined*
pDeviceProtocol 0x0 RNIDSspecificationdefined*
bMaxPacketSize 0x8 =EP0BufferSize
idVendor 0x5e0x04 MicrosoftVendorID
idProduct 0x1,0x00 Microsoft
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- USBRNDIS