USB-RNDIS

RNDIS原理分析--PDD部分

                                      ---------------by nasiry 
                                     转载请说明出处,并通知我
 

1.初始化

首先我们从PDD_Init开始。
所谓初始化的动作通常包含对硬件设备的初始化,一般说来就是通过设置设备寄存器来对设备进行必要的配置,另外一个方面就是软件的初始化,包括全局数据变量的空间申请初始化,中断函数的挂接,其他函数指针的挂接。
既然需要读写设备的状态寄存器设备的端口/地址就是必不可少的,首先PDD_init先检查有没有从MDD传过来的设备地址,如果没有,通过PciFind2890找一下我们需要的2980有没有在PCI以查卡的形式在某一个插槽上,如果继没有由MDD传递过来的设备地址,PCI插槽上也没有我们需要的2890,怎么办?还能怎么办,既然没有的话,我也干不下去了,return False算了,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_COMPLETE IndicateRndisPacketCompleteHandler; 
     PFN_PDD_SET   SetHandler;
     PFN_PDD_GET  GetHandler;
     PFN_PDD_ISR  ISRHandler;
     DWORD   dwIRQ;
     DWORD   dwMaxRx;
     DWORD   dwBaseAddr;
    //  BUS specific information
    //  If it is PCI then this driver will be loaded by PCI enumerator through
    //  NDIS.  
    BOOL    bPCIDevice;
    //  These are only needed for PCI type device AND if the driver chooses
    //  to use GIISR.DLL as default ISR.
    BOOL    CheckPort;            // If true, check port to see if device is asserting IRQ
    BOOL    PortIsIO;             // Port is IO port (possibly true only for x86)
    BOOL    UseMaskReg;           // If true, read from MaskAddr to obtain mask
    DWORD   PortAddr;             // Port Address
    DWORD   PortSize;             // Port data width in bytes
    DWORD   Mask;                 // Mask to use on data port to determine if device is asserting IRQ
    DWORD   MaskAddr;             // Address of register to use as mask

} RNDIS_PDD_CHARACTERISTICS, *PRNDIS_PDD_CHARACTERISTICS;

这个结构包含了一系列的函数指针和地址,中断,和最大接收数等信息,没有提到的这些个结构成员都是PCI设备所需的,这里我们仅仅需要把bPCIDevice设置为False也就是说我们并没有使用PCI设备就可以了。注意这个参数的类型是OUT,MDD也就是通过这里得到设备的信息的。因此在这里需要填充该结构,然后完成对硬件的初始化。

另外还有一个相当重要的结构,这个结构在全局范围内存在一个实例,是我们的PDD工作的中心,我们来看看这个结构的原形。
typedef struct
{   volatile PUCHAR   pucBaseAddress;
    volatile PUCHAR   puc9054Address;
    ULONG    ulIRQ;
    DWORD    dwSysIntr; 
    BOOL    bConnected;
    BYTE    bUsbAddress;
 // EP0 related operations..
    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;
    //  EP1 related operations..
    PDATA_WRAPPER       pEP1DataWrapper;
    //  EP2 related operations..
    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的配置,如果对源码或者这个芯片本身有兴趣的话可以参考NET2890 USB Interface Controller For Revision 2 IC来看具体的代码,我这里仅仅将相应的配置总结出来如下:
RNDIS 2890配置
ENDPOINT
EP0     control(IN/OUT)  enable
 1.FIFO Valid Mode FIFO Flush
 2.EP0 Packet Size =8byte
 3.Handshake receive
EP1(EPA)OUT enable
 BULK mdoe
 Max packet size = 64byte
 handshake receive
EP2(EPB)IN enable
 BULK mode
 Max packet size = 64byte
 FIFO valid and FIFO Flush
EP3(EPC) IN enable
  INTERRUPT MODE
  Max Packet size = sizeof(INTERRUPT DATA)
  FIFO valid and FIFO Flush 
 
从这些配置信息可以看到,在RNDIS的实现中使用了4个ENDPOINT分别创建4个管道在PC与设备之间传输数据和传输控制信号。其中EP0用于USB协议本身的控制,EP1作为Host-Device的上行传输通道,EP2做Device-Host的下行传输通道,这里的上行下行都是相对主机而言的。最后有一个比较特别的就是EP3,这个Endpoint的在这里的作用是用于产生中断,具体的做法就是将该端点的FIFO溢出值等于中断结构体的大小,这样每次得到一个中断数据就马上产生一个中断,结合起来就有了一条具备完整双工通路和控制通路的完整链路用于模拟网卡了。

以下是上面提到的中断结构。
typedef struct _INTERRUPT_DATA
{ DWORD  Notification;
 DWORD  dwReserved;
} INTERRUPT_DATA, *PINTERRUPT_DATA;


设置完了这些端点以后,首先在开启中断之前清除相关的中断标示,以免产生不必要的误动作,这里主要是SOF(Start of frame),Setup Packet Interrupt,root port reset Interrupt status,Input Pin Change Interrupt Status几个状态寄存器。
确定了这些中断不会被误触发之后就开始对中断寄存器进行配置了。

下面照例将这些配置给写下来,以便分析。
EP0
   Data IN Token Interrupt enable
   Data Packet Received Interrupt Enable
   Data Packet Transmitted Interrupt Enable
EP1(EPA)
   Data Packet Received Interrupt Enable 
EP2(EPB)
   Data Packet Transmitted Interrupt Enable

然后打开Setup Packet Interrupt 和Root port reset Interrupt 用于EP0接收并响应复位和配置。USB的硬件初始化的动作到现在就完成了,所以置寄存器位Device configured,告知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.Input Pin Change Interrupt Status|Suspend Request Interrupt Status
前一个状态指的是检查到USB电源状态的改变,对应的动作也就使插拔USB线,第二个状态是受到休眠请求时发出的。这是最紧急的事情,关系到所有其他的代码还要不要运行(线都给拔了难道还能玩模拟802.11?),所以放在最前面。具体的代码执行以下内容,首先:清除中断。然后通过调用MddDisconnect()做软件上断开连接的准备。

2.Root Port Reset Interrupt Status
这个信号对应的是USB根集线器复位的动作,也就是主机复位。实际代码照管理清除中断,冲刷EP0的FIFO,同时无效所有当前的发送动作,这个时候主机已经复位,所以通过调用MddSendRndisMessageComplete强制结束当前的发送数据的动作,和当前还在等待接收的数据包然后通知MDD断开连接。等待重新初始化和配置USB,这里所说的初始化和前面我们上一部分的初始化不同,这里仅仅是将USB的逻辑状态恢复到最先的时候。

3.EndPoint0 Interrupt Status|Setup Packet Interrupt
这些代码中很大部分都是USB Specifications v.1.1的内容,最好对照参看.这个中断服务子程序对应EP0的传输和接收到主机发来的配置信息。EP0又对应了很多的子处理,同样使用了一个大的循环结构来检查和执行USB的各种控制动作。不同的是这次检查的是IRQStatus2和EP0IRQstatus两个寄存器.下面我们仍旧按照检查的先后顺序(优先级由高到低的顺序)来解读这些处理代码。
a.Data Packet Transmitted Interrupt.
 这个中断发生在EP0完成发送数据到Host的动作时发生,也就是说检查EP0现在是否忙,如果在就绪状态下的话就设置USB协议所需的USB地址。
b.Setup Packet Interrupt.
        该中断在USB接收到设置数据包时发生。
 在该状态下又有两个子状态
 b1. Data IN Token Interrupt
 当接收到从主机发来的Data IN标示时产生,这个时候需要调用EP0Send发送一组确认数据完成接收的握手动作,这样主机接下来就可以将数据发送过来了。(to be continue)
 b2. Data Packet Received Interrupt
 由于握手动作以上面已经动作,接收从主机发送过的数据来时该中断产生,故调用完成EP0Receive动作并读入来自主机的数据.B段所示的代码事实上也是通常的接受动作,但需要的注意的是这些代码仅仅在接受请求为设置数据包时动作。

完成了设置数据的读入以后,剩下的所有发送/接收请求
,所以我们需要清除这些非设置数据请求的接收/发送请求。这个动作同样是通过写寄存器的操作完成.剩下的事情就是完成解析并执行Setup Packet的内容了。由于USB协议约定的多数设置都是在这儿完成的所以,导致下面会有很多分支,而每个Setup Packet仅仅包含一个设置信息,也就是完成一项设置之后必须重新读入新的Setup Packet,所以这里的分支并不使用单独的do while结构来实现,取而代之的是两个个switch语句。一个个switch的执行分支的依据是RNDIS定义的请求。另外一个是stand request的类型,我们照例将这些request例下来分别讨论。其中前两个是 Class 或者 Vendor request的分支,后面的则是stand request的分支。
而这个switch语句仅仅处理了以下两个request的类型是Remote-NDIS定义的,这两个请求的属于类驱动或是自定义的请求类型,因此其他所有的请求在这里都将被忽略。在这里顺便简单说明一下USB设备加载的过程,这会有利于了解以下内容,事实上USB设备的加载(这里并不是专门说明USB协议的,简单起见仅仅说正常加载的过程)可以分为5个过程:首先是attach,也就是设备连接到主机的过程,之后很自然的就是powered也就是上电,这两布虽然在USB协议当中相当重要但是和我们的程序代码关系事实上并不很大,之后将会进入一个叫做default的默认状态,在这个状态下设备不会对总线上的任何请求作出响应,直到接收到一个复位信号后设备进入配置地址状态(address),设备复位后会使用一个默认的地址作为设备地址,这个时候将通过默认的EP0建立管道完成Set_address的动作,这样设备就得到一个唯一的设备地址,就可以正式参加条总线上的通讯了,这里说正式仅仅是相对的,这个时候的设备仅仅是建立了最基本的配置物理链路,并不具备真正的数据通讯能力,所以我们还需要进一步配置以达到使用该USB设备通讯/传输数据/控制设备的能力,所以我们需要将设备的类型,名称,使用的驱动类ENDPoint的配置等信息提交给主机后,主机加载上正确的设备驱动程序这样在主机和设备的协同下才能达到我们使USB正常工作的目的,达到了这一步也就就是我们所需要的Configured状态,只有在这个状态下我们才能正常地进行传输。另外还有一点需要说明,在RNDIS协议里面 USB设备使用的是CDC类,也就是说在这些设置需要遵循三个协议:USB,USB CDC,Microsoft RNDIS。好了准备工作已经做好我们就来简要的看看下面的具体代码的内容吧。
下面两个请求的是USB CDC Abstract Control Model所必需的。事实上这是RNDIS所使用的仅有的两个Abstract Control Mode请求。由于采用了CDC Abstract control Model,所以RNDIS要求最少需要两个端点来构造两个管道,一个用于实现管理任务(management找不到合适的中文来描述这个单词),另外一个管道用于实现所需的消息(或者说通告notification),管理元素都可以用请求的形式来实现,所以我们就可以直接使用EP0来实现,而另外一个需求则是来之消息,消息需要像中断一样来通告设备,并让其响应,因此我们使用另外一个专门的Interrupt型端点EEP3来实现。这两部分的实现我们在后面再讨论。
i>SEND_ENCAPSULATED_COMMAND
类型为host to deive 类驱动,接口型请求(见RNDIS和USB specification).

ii>GET_ENCAPSULATED_RESPONSE
类型为Device-to-host 类驱动 接口型 (见RNDIS和USB specification)。
由于使用了USB网络通讯类设备规范,顺其自然地也就使用以上两个类请求。分别为收/发数据包的请求。主要用来传递与网络类设备相关的信息和控制信息。这些收到/发送的命令都要通过MDD来进行分发处理。这个我们以后再进一步介绍。

iii>GET_STATUS
请求类型为device to host endpoint 作用为获得EP0,EP1,EP2三个ENDPoint的状态,并调用EP0PrepareSend将其送至主机。
iv>CLEAR_FEATURE
清除EP0 EP1的stall状态。
V>SET_FEATURE
设置EP0 EP1的stall状态
以上三个请求类型为EndPoint型,因此仅仅是endpoint的操作。
vi>SET_ADDRESS
设置USB Device的USB地址,事实上仅仅是将这个地址送交gRndis2890.bUsbAddress存放,让后让3-a的步骤设置该地址。
vii>GET_DESCRIPTOR
向主机提交各描述符的内容,同样是通过EP0PrepareSend完成的。这些描述符是之前已经定义好了的,具体的设置我列在下面给大家参考。
首先是描述设备类型和型号的Device Descriptor,包含了设备名称,类别,生产厂家等等信息,通过得到的这些信息Host会去找到合适的设备驱动程序在主机上加载,与我们的这部分代码配合运行。注意说明了是RNDIS的描述项部分是RNDIS协议指定了的,不能随便修改否则不能保持和rNDIS协议的兼容。
Device  Descriptor   value  note
bLength   18  length of the Descriptor
DescriptorType  0x1  Device
bcdUSB   0x1,0x1  USB v1.1
bDeviceClass   0x02  Communication Device defined by R-NDIS specification*
bDeviceSubClass  0x0  RNIDS specification defined*
pDeviceProtocol  0x0  RNIDS specification defined*
bMaxPacketSize  0x8  =EP0 Buffer Size
idVendor  0x5e 0x04 Microsoft Vendor ID
idProduct  0x1,0x00 Microsoft geneneric RNDSIMINI Product ID
bcdDevice  0x1,0x0  v 0.1
iManufacturer  0x1  
iproduect  0x2
iSerialNumber  0x3
bNumConfig  0x1

Config Descriptor顾名思义就是配置描述符,里面包含了接口类型,最大功耗(电流),供电方式等等信息,事实上这个配置描述项不仅仅是刚才说的这些作用,更多地,它需要通知主机设备所使用的ENDpoint的情况,使用的类驱动类型,等等信息,我们在下面一个个看一下。在这个描述项中我们可以看到我们定义了2个接口用于实现 RNDIS,其实这也是RNDIS的要求,所需要的两个接口默认接口一个定义为Data Class Interface另一个则是Communication Class Interface。前者用于实际数据流的传输,而后者则用于产生消息(notification).
Config Descriptor   value  note
bLength   0x9  size of the Descriptor
bDescritptor  0x2  Configuration
wTotalLength  0x3e,0x00 Total length of data
bNumInterfaces  0x2  defined by RNDIS Specification
bConfValue  0x1  
iConfiguration  0x0  not used 
bmAttributes  0x40  Self-Powered
MaxPower  0x01  2mA

下面的描述项所描述的内容包含的主要是该设备所使用的接口类型,具体到这里就是指定了使用CDC的通讯的RNDIS接口(0xff自定义的协议)。你说从哪儿看出来是CDC类的?bInterfaceClass =0x02所描述的就是这个信息。下面的这个描述项就是用于产生相应的Communication Class Interface,可以看到这个接口仅仅使用了1个端点,也就是我们前面说过的EP3这个端点被定义成中断型,也就是为了用作传送消息而不是数据。还有一个信息就是借口的编码这里接口的编号为0。
Communication Class INTERFACE descriptor.
Descriptor    value  note
bLength   0x9  Length of the Descriptor
bDescriptorType    INTERFACE
bInterfaceNo  0x00  0  
bAlternateSet  0x00  0
bNumEndPoints  0x01  defined by RNDIS specification
bInterfaceClass  0x02  Communication  Interface Class defined by RNDIS specilication
bIfSubClass  0x02  sub class
bIfProtocol  0xff  vendor defined
iInterface  0x00  not used

由于上面我们在设置了使用USB CDC类,所以下面我们就要按照CDC的规则来办事了。(请参考Universal Serial Bus Class Definitions for Communication Devices请不要嫌协议太多,太麻烦事实上它们并不复杂:P)按照CDC协议的内容FunctionalDescriptor应该还有一段可选的部分也就是Header Functional Descriptor,这里并没有实现该部分,所以就从Call Manageament Functional Descriptor开始,该描述项定义了相关管理调用的约束和方式,具体下面的内容就是:设备发送接收管理信息使用通讯类接口,设备不自己调用管理功能。使用1个Data Class Interface.
Functional Descriptors for Communication Class Interface
CALL  management functional descriptor.
Descriptor    value  note
bFunctionLength  0x5  length of the descriptor
bDescriptorType  0x24  CS_INTERFACE
bDescSubType  0x1  CALL  management functional descriptor.
bmCapabilities  0x00  see sect 5.2.3.2 usb cdc
bDataInterface  0x1  1 data class Interface

Abstract Control Management functional Descriptor定义了Communication Class Interface所支持的命令集合。具体到下面的内容为:设备不支持Send_Break,Set_line_Coding,Set_Control_Line_State,Get_Line_Coding,Set_Comm_Feature,Clear_Comm_Feature,Get_Comm_Feature请求和Serial_State,Network_Connection消息,也就是说禁用所有可选Abstract Control Model*的消息和请求,紧紧保留了必需的SEND_ENCAPSULATED_COMMAND,Get_ENCAPSULATED_COMMAND请求和RESPONSE_AVAILABLE消息。
ABSTRACT Control Management Functional Descriptor 
bFunctionLength  0x4
bDescriptortype  0x24  CS_INTERFACE Class Interface
bDescSubType  0x02   ABSTRACT Control Management Functional Descriptor
bmCapabilities  0x00  See sect 5.2.3.3 USB CDC
UNION function descriptor顾名思义就是起到联系各个独立的Interface,描述其之间的关系的作用。具体到代码内容:Interface0也就是现在正在定义的Communication Class Interface为(master)主模式,InterFace的编号在bInterfaceNo中指定.Interface1就是后面将会定义的Data Class Interface为从模式。
UNION function descriptor
bFunctionLength  0x5  length of function
bDescriptortype  0x24  CS_INTERFACE Class Interface
bDescSubType  0x06  UNION function descriptor
bMasterIf  0x0  Interface0 is the master if.
bSlaveIf  0x1  Interface1 is the slave if.
下面这个描述项的内容是接口所用的端点信息,告知主机与该端点的信息,并与该端点连接构成的管道为上面定义的Communication Class Interface所用。至于端点本身的信息我们在第一节里面已经讲过了,就不再重复。此外bInterval描述的信息为主机查询该端点的间隔时间也就是说主机会在每隔1ms的时间内检查该端点,所以我们的"中断"到响应的过程本质上是通过查询完成的,传输时中断响应自然会有延迟。
Endpoint descriptors for Communication Class Interface
Descriptor    value  note
bLength   0x7  Descriptor length
bDescriptorType    ENDPOINT
bEndpointAddr  0X83  IN - EP3
bmAttributes  0x03  Interrupt endpoint
wMaxPacketSize  EP3LEN  
bInterval  0x1   1 ms polling from host

下面的描述项就是我们用于传送数据的Data Class Interface Descriptor,该端点不使用任何传输协议。接口编号为1。使用两个端点也就是下面的EP1和EP2.
Data Class INTERFACE descriptor.
Descriptor    value  note
bLength   0x9  Descriptor length
bDescriptorType    INTERFACE
bInterfaceNo  0x1
bAlternateSet  0x0
bNumEndPoints  0x2  RNDIS spec   
bInterfaceClass  0xa  Data if class RNDIS spec
bIfSubClass  0x0  unused.
bIfProtocol  0x0  unused.
iInterface  0x0  unused. 
以下两个就是我们前面提到Ep2和Ep1的描述项,同样这两个描述项描述了端点的内型信息,并在相应的事件/请求产生时将这些信息提交给主机。端点的具体配置就不再重复了,下面的bInterval是有点特别,使用了0作为间断时间,这是因为我们将这两个端点设定为Bulk模式也就是块传送模式,在这个模式下不需要定时查询。
Endpoint descriptors for Data Class Interface
Descriptor    value  note
bLength   0x7  Descriptor length
bDescriptorType  ENDPOINT ENDPOINT [OUT]
bEndpointAddr  0x01  OUT -- EP1
bmAttributes  0x02  BULK
wMaxPacketSize   EP1LEN, 0x00 EP1Len
bInterval  0x0  ignored for BULK.

bLength   0x7  Descriptor length
bDescriptorType  ENDPOINT ENDPOINT [IN]
bEndpointAddr  0x82  IN -- EP2
bmAttributes  0x02  BULK
wMaxPacketSize   EP2LEN, 0x00 EP2Len
bInterval  0x0   ignored for BULK.

下面的描述项的内容是字串描述项,这些描述项的内容包括支持的语言,生产厂商的名称,产品的名称。没什么好说的。
String Descriptor   value  note
//Supported Language
bLength   0x04  //Length
bDescriptor  0x03  //String
wLANDID[]  0x0409      //English only
//Manufacturer
bLength   0x10  //Length
bDescriptor  0x03  //String
wLANDID[]  "Manufacturer"  //name of the manufacturer
//Product
bLength   0x02+sizeof(wLANDID)  //Length
bDescriptor  0x03  //String
wLANDID[]  "Sample Microsoft RNDISMINI implementation on NetChip 2890 USB function controller"  //name of the product
//Serial Number
bLength   0x02+sizeof(wLANDID) //Length
bDescriptor  0x03   //String
wLANDID[]  "1234567890ABCDEF"      //Serial number

viii> GET_CONFIG
读取设备的配置状态,事实上这个配置状态是由host决断的,这里的配置状态在初始化的时候被设置为0,待到主机完成对设备的配置的时候主机会通过下面将会提到的Set_config来设置这个状态标签。使用EP0PrepareSend发送状态信息。
ix>GET_INTERFACE
同样的道理,这儿的Interface的状态信息同样也是用下面的Set_interface来设定的,在这里返回的Interface的状态值就仅仅是前面的保存值重新读出而已,没什么太大的问题。这里同样使用EP0PrepareSend发送状态信息。
x>SET_CONFIG
xii>SET_INTERFACE
这两项的功能在上面已经说过了就不再重复。
xi>SET_DESCRIPTOR     
Set_Descriptor本来是用来设定Descriptor的值,让设备以别的形式/功能来重新加载用于同一接口(指物理接口不是USB的Interface)上多个功能驱动的切换,但这里仅仅使用了一个指定的功能(事实上大多数时候就是如此)所以Set_Descriptor没有进行任何操作。

在EP0ISR的最后还有一个关于握手的动作,因为我们开始的时候定义了EP0的接收是按照握手模式进行的,所以需要操作寄存器完成相应的动作如果有注意到上面的代码段里面对应发送动作都专门做了bControlStatusHandShake = FALSE的动作,其目的在于不对任何接收动作作出响应,这样保障发送动作顺利进行。下面我们继续看其他部分的内容。

4.EndPoint1 Interrupt Status
这个中断产生的原因是EP1的输入FIFO上溢出(接收满了)。中断处理程序的开始,首先是得到FIFO计数寄存器和FIFO状态寄存器的内容,检查状态位上溢和下溢标示位是否设定,如果不是溢出的原因代码执行到这儿就需要调试了。如果目前没有数据包正在接收的话,就意味着这是一个数据包的开始,就需要为接收数据包分配需要的空间,然后将FIFO内的数据送到刚才分配好的内存单元,如果已经有数据包在传输了我们就无需初始化/分配内存的动作,仅仅需要完成数据从FIFO到本地内存的搬运。如果已经到了数据包的结束,则还需要调用MddIndicateRndisPacket通告MDD数据包已经传送结束,完成对数据包的处理。所有的处理结束之后就只需要清除中断,然后通过设置Control Status Phase Handshake状态寄存器使得EPA(EP1)恢复对主机的应答,继续传送数据。在2890的DataSheet上有这样的说明:"This bit(Control Status Phase Handshake)is only used for endpoint0",不过在这里还是使用了这个状态位,让我有点迷惑。这中间还有一个排错的动作,也就是接收到的数据包如果超过了允许的大小,就冲刷掉该数据包。在这里中断动作下的,数据接收就完成了。

5.EndPoint2 Interrupt Status
这个中断产生的原因EP2的输出FIFO下溢出(发送完毕,空了)。在一开始同样是获取,首先是响应该中断,注意接收的过程是先动作再响应,而发送的过程则刚刚相反。之后便是读取FIFO计数寄存器和FIFO状态寄存器的内容,然后清除该端点的上溢出/下溢出状态表示位。设置FIFO Valid使得在FIFO填充好之前该端点不会将数据送出,然后检查FIFO Valid这个时候是否已经被清除,如果已经被清除则说明数据包的长度为0,这个时候直接通告MDD发送已经结束就可以了。FIFO Valid仍然保持就需要做发送数据的工作了:1.按照FIFO空余的空间填充数据待传送.2.如果待传送的数据长度小于EP2的块大小,就需要自己动手设置一下FIFO Valid,保证数据正确地送出。

中断服务程序的概貌就是这个样子,可以看到,这里包含了对设备的配置,端点FIFO的接收/发送控制等内容。相对来说这个中断服务程序还是相当复杂的,RNDIS协议的要求主要是体现在配置代码段,这些配置信息将会贯穿整个程序的执行流程。

 


 

 

to be continue...

 

posted @ 2004-09-22 11:04  nasiry  阅读(31066)  评论(26编辑  收藏  举报