Windows XP下usbport.sys驱动内部实现解析(四)
然后来看看实际上的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