Chinaunix首页 | 论坛 | 博客
  • 博客访问: 244922
  • 博文数量: 181
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 422
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-09 15:34
个人简介

你好 世界

文章分类

全部博文(181)

文章存档

2016年(181)

我的朋友

分类: WINDOWS

2016-06-04 16:33:43

Windows XP下usbport.sys驱动内部实现解析(二)
2010-01-22 18:15
(接上一篇)

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。

阅读(680) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~