分类: WINDOWS
2012-03-18 14:21:16
(接上一篇) 4. 处理USB请求(URB) 在进入下一个主题之前我总结几个事实让大家注意: 1. 对于每个usb总线上的设备,usbport在usbhub的帮助下为其创建一个device handle,并把这些device handle链接到一起,并为endpoint 0 创建一个pipe handle。 2. 在进行select configuration的时候,usbport创建一个config handle结构,保存其指针到对应的device handle,然后创建若干个interface handle,全部链接到config handle上去。对于每个interface里面的全部endpoint,为其创建一个pipe handle,全部连接到对应的device handle上面去。对于每个pipe handle,为其创建一个endpoint结构,保存其指针到pipe handle,并把所有创建的endpoint全部链接到一起。 3. 客户驱动必须要保存由select configuration所返回的pipe handle,并在随后的urb里面恰当的填写这个值。 前面的结构中的若干个LIST_ENTRY就是usbport维护管理这些结构的关键。维护管理用的基础结构了解好了,接下来就是urb的处理问题了。 说到urb的处理,就不能不提一些internal io control的问题。都知道urb是通过一个internal io control提交的,除了这个以外还有若干个其他的internal io control,比如用于获取port状态的、比如用于reset port的、等等等等。这些处理这里先不提,因为大部分的处理都是由usbhub完成的,usbport单独完成的功能很少,还有部分是两个配合完成的 -- 这个留到usbhub的地方再回头来说。 首先明白一个事实,客户驱动把urb提交给的是自己的pdo,这个pdo作一些过滤动作以后,直接提交给了root hub的pdo。至于是不是这样的一个情况,等到说起usbhub的时候就明白了,现在先假定如此。 usbport在一份数据结构的帮助下对每个urb作一些检查,然后转到特定的处理函数,先看这个数据结构的定义: struct URB_DISPATCH_TABLE // sizeof=0X14 { PVOID DispatchRoutine; // process routine USHORT TransferLen; // UCHAR reserved[2]; UCHAR bmRequestDirection; // setup packet UCHAR bmRequestType; UCHAR Recipient; UCHAR bRequest; UCHAR Flags; UCHAR data[3]; ULONG FunctionCode; }; 很简单的一个结构。TransferLen用于固定大小的传输,用来检查说提交的buffer大小是否正确,如果是0,则不检查大小信息。接下来的几个成员用于control transfer用来填充setup packet。许多的urb都不需要你完全的填充所有的setup packet成员,usbport在这里会为你填充这些已知的固定的成员。flags则是控制usbport的操作方式的,下面会详细的解释。 function code则是指明这份数据是对应哪个function的。理所当然的,usbport为每个function code准备一个这样的结构构成一个数组。 下面是处理URB的代码: processurb(pUrb) { 检查FunctionCode 检查pUrb->DeviceHandle if(UrbDispatchTable[FunCode].Flags & 4) { // force usbport to use default pipe get pipe handle from device handle save it to pUrb } if(UrbDispatchTable[FunCode].Flags & 1) { // actual transfer needed if(UrbDispatchTable[FunCode].Flags & 8) { // no transfer buffer needed pUrb->TransferBuffer = 0 pUrb->TransferBufferLength = 0 pUrb->TransferBufferMdl = 0 } validate pipe handle if(pUrb->TransferBufferLength !=0 && pUrb->TransferBufferMdl == 0) { IoAllocateMdl(); MmBuildMdlForNonPagedPool(); set a flag in UrbHeader,indicates that when we complete this urb,we must free the mdl } allocate a transfer struct } check transfer length status = call UrbDispatchTable[FunCode]->DispatchRoutine(...); if(status != pending) complete the urb return status; } proccess urb是一个公共的处理函数,他根据dispatch table的flags成员作一些有限度的处理,然后交给真正的dispatch routine处理。 这个dispatch routine就各式各样了,大致分成4类: 1类就是不需要transfer结构了,参考上flags & 1非0的分支。这类urb大多直接完成了,比如select configuration,比如get frame length。 2类属于control 传输,这类就根据dispatch table里面的那些setup packet成员填充自己的setup pack结构,然后将transfer排队。 3类属于interrupt or bulk 传输,直接排队transfer。 4类属于iso transfer,最主要的是要检查各个Packet,而且要设置start frame number,最后还是要排队transfer。 不需要排队的urb大多是一些没有实现的urb,比如set frame length,或者是一些要特别处理的,比如select configuration。其他的最终都是要排队transfer的。特别注意参加排队的是transfer结构,而不是irp或者其他。对照 endpoint结构的那些成员PendingTransferList等等就能明白,排队的对象并不是irp。 transfer 也是一个不小的结构: struct TRANSFER // sizeof=0XC0 { ULONG Direction; ULONG TimeoutInterval; LARGE_INTEGER SubmitTime; ULONG TransferedLen; ULONG Status; ULONG Irp; PKEVENT pEvent; PURB UrbPointer; LIST_ENTRY TransferListEntry; // link entry ULONG MappedRegisterBase; // for dma ULONG NumberOfMappedRegister; // ULONG TransferDirection; ULONG TransferBufferLen; URB_SETUP_PACKET SetupPacket; PMDL TransferMdl; LIST_ENTRY AdapterDBList; // for double buffer PVOID ParentPointer; // split PVOID EndpointPointer; PVOID ClientTransferPointer; // passed to miniport LIST_ENTRY ChildTransferListHead; LIST_ENTRY SplitTransferListEntry; PVOID IsoTransferInfo; // iso SG_LIST sgList; // scatter-gather list }; 留下了一些分析相关的成员:其中如果这个transfer对应有irp则会设置Irp成员,这个是可以选的。如果没有对应irp也是可以的,那么怎么知道这个transfer完成了呢?用irp的话还可以使用complete routine,要是没有irp呢?这就是下面的那个pEvent成员的作用了,他指向一个event,完成的时候会设置这个event。 还有几个list entry,存在的主要原因就是允许传输大于MaxTransferLength的数据,那么就必须把原来的buffer切割成小的buffer,为每个buffer创建一个child transfer,这些list entry就是用来管理这个的。 至于AdapterDBList,则是上面说的某个buffer跨越了非连续的两个物理页的情况下,用于miniport通知的。 miniport必须要使用额外的缓冲而不是transfer所提供的缓冲,所以usbport必须要提供空间来保存这些额外的缓冲信息。 最后的是sgList,usbport把要传输的buffer映射成一个一个的物理页,用sgList这个结构来描述,这个结构会传递给miniport driver使用。 好了,来看真实的排队情况:usbport通过调用_USBPORT_QueueTransferUrb函数来排队某个urb,这个函数很简单。 _USBPORT_QueueTransferUrb(pUrb,pEndpoint) { do some check update some fields in transfer struct if(transfer associates with an irp) call _USBPORT_QueuePendingTransferIrp else call _USBPORT_QueuePendingUrbToEndpoint call _USBPORT_FlushPendingList } 根据transfer是否关联有irp调用不同的函数,在有irp的情况下首先是要设置irp的cancel routine,然后再调用_USBPORT_QueuePendingUrbToEndpoint函数。也其实就是多一个设置cancel routine的步骤,至于cancel部分后面会有专门的讲解,先放一放。主要来看后面这个函数,更是非常简单: _USBPORT_QueuePendingUrbToEndpoint(pTransfer,pEndpoint) { link transfer->TransferListEntry to Endpoint->PendingTransfer } 接下来当然是_USBPORT_FlushPendingList函数了,看他的名字都知道是在干什么。这个函数显得很复杂,因为是几个很关键的函数之一。 _USBPORT_FlushPendingList(pEndpoint) { bContinue = TRUE do { if(Endpoint is not root hub\'s endpoint) { check Endpoint->ActiveTransfer list if(is not empty) { get a transfer from active list bContinue = FALSE call _USBPORT_CoreEndpointWorker if return != 0 call _USBPORT_InvalidateEndpoint } } if(bContinue == FALSE) break; check Endpoint->PendingTransfer if(is not empty) { reset canel routine if(irp has not been canceled) { bContinue = FALSE call _USBPORT_QueueActiveUrbToEndpoint if( return != 0 ) call _USBPORT_FlushMapTransferList else { call _USBPORT_CoreEndpointWorker if return != 0 call _USBPORT_InvalidateEndpoint } } } while( bContinue ); } 或者看了会很奇怪,先不管,我把全部代码流程都列出来,然后再总体讨论。 _USBPORT_QueueActiveUrbToEndpoint(pTransfer,pEndpoint) { bNeedMap = FALSE if(Endpoint is stopped || Transfer is aborted) link transfer to pEndpoint->CancelTransfer else { if(Transfer\'s length != 0) { link transfer to fdo\'s MapTransferList bNeedMap = TRUE; } else { link transfer to pEndpoint->ActiveTransfer } } return bNeedMap } 这个函数比较简单,作作判断决定transfer该进入什么样子的list,然后返回一个标记表明是否需要进行map,只有在transfer的 transfer length也就是pUrb->TransferBufferLength非0的时候,才需要进行Map。 _USBPORT_FlushMapTransferList { while(!pFdoExt->DoMapping) { if(pFdoExt->MapTransferList is not empty) { pFdoExt->DoMapping = TRUE; get a transfer from the list AllocateAdapterChannel(_USBPORT_MapTransfer); } else break; } } 也是一个不算复杂的函数:首先检查当前是否在map,如果为false,然后检查map transfer list是否是空,不空则取一个处理调用AllocateAdapterChannel,传递的参数是_USBPORT_MapTransfer,在这个函数里面会重新设置pFdo->DoMapping = FALSE。 |