Windows XP下usbport
+ -

Windows XP下usbport.sys驱动内部实现解析(四)

2024-09-27 2 0

然后来看看实际上的work routine,先看通用的这个。root hub的是一个特殊的函数。

USBPORT_DmaEndpointWorker(pEndpoint)
{
    call _USBPORT_GetEndpointState(pEndpoint)

    curState = return value
    if(curState == active)
    nextState=_USBPORT_DmaEndpointActive(pFdo,pEndpoint)
    else if(curState == pause)
    nextState=_USBPORT_DmaEndpointPaused(pFdo,pEndpoint)
    else
    ASSERT(0);

    _USBPORT_FlushCancelList(pEndpoint)

    if(nextState != curState)
        _USBPORT_SetEndpointState(pEndpoint,nextState)
    else if(nextState == pause)
        _USBPORT_InvalidateEndpoint(pEndpoint)
}

很简单的一个函数,根据当前endpoint的state调用不同的函数,函数的返回值是新的状态。如果跟以前的状态不同,则要调用一次set state函数,这个函数完成的功能就是我上面说说的那样,把endpoint放入state change list,然后告诉miniport driver引发一次start of frame的中断。

对于一直处于pause状态的endpoint,不断的排队 CoreWorker调用,直到离开pause状态为止,为什么要这样呢?处于active状态的endpoint,corewoker的调用自然有 miniport通过invalidate来调用,但是pause状态却没有人去调用invalidate,只好自己来了。

接下来来看两个处理函数:

_USBPORT_DmaEndpointActive(pFdo,pEndpoint)
{
    nextState = active
    while(pEndpoint->ActiveTransfer is not empty)
    {
        go through the list,for each
        if(pTransfer had been marked to force complete)
        {
        _USBPORT_QueueDoneTransfer(pTransfer)
        break;
    }

    if(pTransfer had been submitted)
    {
        if(pTransfer had been marked canceled or aborted)
        {
            nextState = pause
            break;
        }
        else 
            continue;
    }

    if(pTransfer is iso transfer)
    {
        call miniport\'s IsoTransfer Routine
        retValue = return value
    }
    else
    {
        call miniport\'s SubmitTransfer Routine
        retValue = return value
    }

    if(retValue == sucess)
    {
        set this transfer has been submitted
        set transfer\'s timeout value
    }
    else if(retValue == need_to_wait)
        break;
    else
    {
        ASSERT(pTransfer is Iso transfer)
        _USBPORT_ErrorCompleteIsoTransfer
        break;
    }
    return nextState
}

也 是个比较简单的函数,只是几个特殊情况要作一下说明:首先是第一个if判断,为什么会有些transfer被标记成了强制完成呢?主要是因为short packet ok这个标记允许传输比你设置的buffer要小的数据,就可能发生这样的情况。比如你期望传递1000个字节的数据,这个urb在提交的过程中可能超过 了最大传输长度,以至于被切成了两个transfer提交给硬件,而硬件却只是传输了其中的100个字节,这样只是完成了第一个transfer。这个时 候usbport检测到这种情况,标记第二个transfer为强制完成状态。这也就是这个标记的来源。

另 外的测试是关于cancel跟abort的,cancel跟abort实际上都只是设置了一个标记,然后就返回了。真正的cancel跟abort的操作 还很多,比如transfer结构的内存释放等等,但是如果这个transfer是已经提交给miniport了,就必须要先从miniport那里收回 这个transfer的控制权才可以。所以并不是在调用cancel或者abort的时候就能完成全部的操作的,只是设置了一个标记,这个部分马上就会讲 到。

接着注意:transfer一直都在active list里面,并没有取出来作处理,而只是单纯的遍历操作,所以有个submitted标记表示是否有提交给硬件。

至于timeout,是在提交urb的时候设置的一个参数。如果你希望urb只是占用一定的时间进行处理,不管完成了没有,都返回的话就可以使用这个功能。时间上urbport设置了一个定时器,周期的检查每个endpoint的active list里面设置了submitted标记并且启用了timeoute功能(也是设置一个标记)的transfer,时间到了当然是设置起abort标记了,下次endpoint的workroutine运行的时候就能检测到这种情况加以处理了。

transfer最终被提交给了miniport的相关routine,这个routine返回一个值表示自己还能继续接受transfer或者必须要等待或者是iso出错(其他的类型的transfer出错不是这样处理的)。

注意几个循环继续跟退出的条件以及返回值的变化:

在说pause处理之前,就必须要说cancel跟abort了,因为pause状态是专门为他们这两个操作设计的。

cancel发生在调用IoCancelIrp的时候,而abort则是发生在客户驱动abort pipe的时候。先说cancel,irp的cancel是个很经典的问题,随便找个写驱动的书上都会讲到(如果没有,那就扔了别看那个垃圾书了)。cancel最最主要的问题就是同步,这里就省略了,这种大众化的东西随便都能找到介绍。

usbport对cancel的处理方式是这样的:
先设置irp的cancelroutine
然后检查irp->Cancel值
如果为false就表示irp没有被cancel,正常处理
如果为true就将cancelroutine重新设置成0
如果返回得值非0就表示cancelroutine还没有被调用,则complete这个irp,否则表示cancelroutine已经被调用了,正常处理
正常处理就是把irp保存到一个数组里面(当然得在spinlock保护下)

而cancel routine里面则先到数组里面去寻找irp,如果没有找到就直接返回了,找到了则complete这个irp。cancel routine的语意是简单的,任何被cancel的irp都必须存在于那个irp的数组里面,不存在的就表示这个irp已经被完成了。

设置cancel routine的地方就必须要保证这个语意,没有被cancel的irp,将来就可能被cancel,就必须要加入到数组里面。正在被cancel的irp,因为cancel routine要从数组里面寻找这个irp,就可以选择是自己完成还是让cancel routine来完成。

CancelRoutine(pIrp)
{
    IoReleaseCancelLock()
    KeAcquireSpinLock(&IrpTableLock,..)
    if(FindIrpInIrpTable(pIrpTable,pIrp))
        RemoveIrpFromIrpTable(pIrpTable,pIrp)
    else 
        pIrp = NULL;
    KeReleaseSpinLock(&IrpTableLock,..)

    if(pIrp)
        IoCompleteRequest(pIrp,...)
}

EnqueueIrp(pIrp)
{
    KeAcquireSpinLock(&IrpTableLock,..)
    IoSetCancelRoutine(pIrp,CancelRoutine)
    if(pIrp->Cancel)
    {
        pCancel = IoSetCancelRoutine(pIrp,0)
        if(pCancel)
        {
            KeReleaseSpinLock(&IrpTableLock,..)
            IoCompleteRequest(pIrp,..)
            return;
        }
    }

    InsertIrpToIrpTable(pIrpTable,pIrp)
    KeReleaseSpinLock(&IrpTableLock,..)
}

DequeueIrp
{
    KeAcquireSpinLock(&IrpTableLock,..)
    pCancel = IoSetCancelRoutine(pIrp,0)
    if(pCancel)
        RemoveIrpFromIrpTable(pIrpTable,pIrp)
    else
        pIrp = NULL

    KeReleaseSpinLock(&IrpTableLock,...)

    if( pIrp )
        Contine to process the irp
}

这个部分没有什么特别的,irp的cancel模型到处都能找到。

usbport 所使用的模型就是上面我描述的这样,只不过对于不同阶段的transfer、cancel所表征的语意是不同的。对于pending阶段(也就是还在 pEndpoint->PendingTransfer list里面的transfer),cancel作的工作很简单,直接释放掉这个transfer所占的内存就够了。对于map阶段对于active阶段 就不行了,必须要作额外的工作,比如allocated的channel必须要free,对于提交到硬件的transfer必须要收回,这些工作就没有办 法在cancel routine里面来完成了。那么真正的cancel是怎么进行的呢(只是针对非pending list的transfer)?

被分成了两步,首先是相关irp的完成,这个部分没有任何疑问直接用STATUS_CANCELED完成这个irp。第二步就设置到内存,dma资源的是否问题了,这个是在endpoint的workroutine的帮助下完成的。

对于要cancel的transfer,cancel routine简单的给他设置一个标记,表示这个transfer已经被cancel掉了,然后InvalidateEndpoint一下,排队 CoreWorker调用,进而进入endpoint的workroutine(假定当前状态是一个active状态)。上面的代码可以看到active状态检查到active list里面有设置了cancel标记的transfer的时候,endpoint经过若干个步骤以后就转入到了pause状态,进入了下面这个函数:

_USBPORT_DmaEndpointPaused(pFdo,pEndpoint)
{
    pTransfer = pEndpoint->ActiveTransfer->Flink
    nextState = active
    while(pTransfer != &pEndpoint->ActiveTransfer)
    {
        if(pTransfer had not been marked canceled and aborted)
        {
            pTransfer = pTransfer->TransferListEntry.Flink
            continue
        }

        if(pTransfer had been submitted)
        {
            ask miniport to abort this transfer
        }

        remove this transfer from the active list

        if(pTransfer is a child transfer)
            call _USBPORT_CancelSplitTransfer
        else
            insert this transfer to endpoint\'s CancelTransferList
    }
}

USBPORT_CancelSplitTransfer函数很简单,把这个child transfer从其parent的children list里面remove出来然后释放掉自己,如果其parent的children list为空了,则把parent加入到endpoint\’s CancelTransferList里面。

pause 的操作很简单,加入那些cancel和abort的transfer到CancelTransferList里面,那么什么时候完成他们的呢?往上看你就 发现了在调用pause处理函数以后立即就有一个FlushCancelList的调用,这个函数的功能自然就水到渠成了,把 CancelTransferList里面的transfer一一取出来,释放掉为他们分配的资源。这里就不列举他们的代码了。

至于abort,跟上面的操作几乎一样。

回头来问一个问题,上面我们都假定transfer已经进入了active list了,如果transfer还位于map list怎么办?上面代码已经给出来了,他们直接就进入了cancel list,而没有去作映射也没有进入active list。

最后最后了,active list里面的transfer在Invalidate的帮助下源源不断的提交给硬件,那pending list呢?map list呢?那是怎么让transfer从pending list进入到map list然后进入到active list里面的呢?

实际上还是由InvalidateEndpoint来完成的,我们知道InvalidateEndpoint设置一个Event,通告一个work thread停止等待,work thread唤醒以后作的第一件事情就是调用函数 _USBPORT_FlushAllEndpoints。这个函数的功能自然就不言而喻了,他把每个非close状态的endpoint收集到一个list里面,然后为这个list里面的每个endpoint调用一次_USBPORT_FlushPendingList。

最后当然是urb的完成了,上面已经由代码展示了这个完成的处理:调用_USBPORT_QueueDoneTransfer函数把这个完成了的 transfer放入到fdo的DoneTransferList里面,然后排队一个dpc,在这个dpc的处理函数里面把他们一一取出来完成,自然少不了调用InvalidateEndpoint函数了。

作个总结

大部分urb被usbport排队,先进入pending list,然后在FlushPendingList的时候进入Map list,在MapTransfer的时候进入ActiveList,在CoreWorker的时候提交给硬件。

Pending list里面的transfer的cancel跟abort只是直接释放资源。

map list跟active list里面的cancel分成两步,一个是irp的cancel完成,一个是资源的释放。资源的释放被推迟了,cancel routine为其设置了一个标记,当coreworker检查到这个标记以后,endpoint进入pause状态,在这个状态下,收集所有设置了 cancel跟abort标记的transfer进入cancel list,在FlushCancelList中再cancel掉他们。

写完了..今天的文章..

比较的乱,自己都觉得…

毕竟usbport本身就是一个400多K的sys文件,似乎不应该是这样说说就能完完全全说明白的。实际上还有好多好多的东西都没有涉及到,比如电源管理,远程唤醒等等,毕竟是一个很大的驱动。。。

原文转自:https://blog.csdn.net/killcpp/article/details/7287118

0 篇笔记 写笔记

Windows复位USB集线器HUB端口设备RestartUsbPort
本人描述了如何在 Windows 下使用IOCTL_USB_HUB_CYCLE_PORT 重新启动USB端口。具体过程为:通过给定的设备实例ID在 Windows设备管理中查找USB 备、确定使用的 USB 端口号、获取其父设备(其 USB 集线器)、打开集线器并执行 IOCTL_USB_HUB_C......
Windows XP下usbport.sys驱动内部实现解析(一)
讲了USB驱动栈整体结构,说明了usbport.sys的重要作用,现在就具体分析usbport.sys的内部实现细节。首先再重复一下:usbport 是一个USB主机控制器的port driverusbuhci是uhci类型的USB主机控制器的miniport driverusbehci则是e......
Windows XP下usbport.sys驱动内部实现解析(二)
4. 处理USB请求(URB)在进入下一个主题之前我总结几个事实让大家注意:对于每个usb总线上的设备,usbport在usbhub的帮助下为其创建一个device handle,并把这些device handle链接到一起,并为endpoint 0 创建一个pipe handle。在进行se......
Windows XP下usbport.sys驱动内部实现解析(三)
_USBPORT_MapTransfer是个比较复杂的函数了,他涉及到transfer的切割、sgList结构的填写,少安毋躁。。。哈哈struct SG_LIST{ ULONG Flags; PVOID CurrentVa; PVOID MappedSysAddress......
Windows XP下usbport.sys驱动内部实现解析(四)
然后来看看实际上的work routine,先看通用的这个。root hub的是一个特殊的函数。USBPORT_DmaEndpointWorker(pEndpoint){ call _USBPORT_GetEndpointState(pEndpoint) curState = ......
关注公众号
  • HID人机交互
  • Linux&USB
  • UAC音频
  • TYPE-C
  • USB规范
  • USB大容量存储
  • USB百科
  • USB周边
  • UVC摄像头
  • Windows系统USB
  • 音视频博客
  • 取消
    感谢您的支持,我会继续努力的!
    扫码支持
    扫码打赏,你说多少就多少

    打开支付宝扫一扫,即可进行扫码打赏哦

    您的支持,是我们前进的动力!