Windows XP下usbport.sys驱动内部实现解析(二)
4. 处理USB请求(URB)
在进入下一个主题之前我总结几个事实让大家注意:
对于每个usb总线上的设备,usbport在usbhub的帮助下为其创建一个device handle,并把这些device handle链接到一起,并为endpoint 0 创建一个pipe handle。
在进行select configuration的时候,usbport创建一个config handle结构,保存其指针到对应的device handle,然后创建若干个interface handle,全部链接到config handle上去。对于每个interface里面的全部endpoint,为其创建一个pipe handle,全部连接到对应的device handle上面去。对于每个pipe handle,为其创建一个endpoint结构,保存其指针到pipe handle,并把所有创建的endpoint全部链接到一起。
客户驱动必须要保存由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。