chengtao
驱动牛犊
驱动牛犊
  • 注册日期2002-11-30
  • 最后登录2020-03-25
  • 粉丝0
  • 关注0
  • 积分11分
  • 威望78点
  • 贡献值0点
  • 好评度17点
  • 原创分0分
  • 专家分0分
  • 社区居民
40楼#
发布于:2007-03-10 02:54
tiamo出品,必是精品
sudami
驱动牛犊
驱动牛犊
  • 注册日期2007-03-01
  • 最后登录2010-03-05
  • 粉丝0
  • 关注0
  • 积分4分
  • 威望30点
  • 贡献值0点
  • 好评度16点
  • 原创分0分
  • 专家分0分
41楼#
发布于:2007-03-01 11:58
看的不是很明白..
harmonic
驱动牛犊
驱动牛犊
  • 注册日期2006-12-07
  • 最后登录2007-04-27
  • 粉丝0
  • 关注0
  • 积分110分
  • 威望12点
  • 贡献值0点
  • 好评度11点
  • 原创分0分
  • 专家分0分
42楼#
发布于:2007-02-28 13:55
置顶啊!
MichaelLi
驱动牛犊
驱动牛犊
  • 注册日期2003-10-02
  • 最后登录2009-02-10
  • 粉丝0
  • 关注0
  • 积分700分
  • 威望70点
  • 贡献值0点
  • 好评度70点
  • 原创分0分
  • 专家分0分
43楼#
发布于:2007-01-23 11:43
研究 研究
caesun
驱动小牛
驱动小牛
  • 注册日期2001-12-10
  • 最后登录2006-12-01
  • 粉丝0
  • 关注0
  • 积分290分
  • 威望30点
  • 贡献值393点
  • 好评度21点
  • 原创分0分
  • 专家分0分
44楼#
发布于:2005-07-02 02:49
记号
xxlangyun
驱动小牛
驱动小牛
  • 注册日期2004-11-22
  • 最后登录2016-01-09
  • 粉丝0
  • 关注0
  • 积分272分
  • 威望58点
  • 贡献值0点
  • 好评度7点
  • 原创分0分
  • 专家分0分
45楼#
发布于:2005-06-26 14:21
非常好,多顶几下。。。。。。
hannah300
驱动牛犊
驱动牛犊
  • 注册日期2005-05-25
  • 最后登录2005-05-26
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
46楼#
发布于:2005-05-25 17:15
so巨大的工作量,pfpf!
biantian
驱动牛犊
驱动牛犊
  • 注册日期2005-04-14
  • 最后登录2005-05-22
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
47楼#
发布于:2005-05-22 02:24
赞一个
转一个
谢谢
wonder_2001
驱动小牛
驱动小牛
  • 注册日期2002-07-02
  • 最后登录2010-12-03
  • 粉丝0
  • 关注0
  • 积分452分
  • 威望96点
  • 贡献值0点
  • 好评度36点
  • 原创分0分
  • 专家分0分
48楼#
发布于:2005-05-19 11:15
致顶。
lsvine
驱动小牛
驱动小牛
  • 注册日期2005-03-09
  • 最后登录2009-10-30
  • 粉丝0
  • 关注0
  • 积分1分
  • 威望57点
  • 贡献值0点
  • 好评度37点
  • 原创分0分
  • 专家分0分
49楼#
发布于:2005-05-18 16:55
学习
天行健,君子以自强不息 地势坤,君子以厚德载物
lijianfei77
驱动牛犊
驱动牛犊
  • 注册日期2005-03-26
  • 最后登录2007-09-11
  • 粉丝0
  • 关注0
  • 积分100分
  • 威望10点
  • 贡献值0点
  • 好评度10点
  • 原创分0分
  • 专家分0分
50楼#
发布于:2005-05-18 12:12
精神可嘉!!!!
shilonglea
驱动牛犊
驱动牛犊
  • 注册日期2004-03-19
  • 最后登录2008-04-17
  • 粉丝0
  • 关注0
  • 积分60分
  • 威望6点
  • 贡献值0点
  • 好评度6点
  • 原创分0分
  • 专家分0分
51楼#
发布于:2005-04-08 14:02
好帖
ihhv2004
驱动牛犊
驱动牛犊
  • 注册日期2004-11-30
  • 最后登录2010-10-28
  • 粉丝0
  • 关注0
  • 积分2分
  • 威望26点
  • 贡献值0点
  • 好评度6点
  • 原创分0分
  • 专家分0分
52楼#
发布于:2005-04-03 12:38
想向tiamo请教:怎样用iasl反编译.sys文件,我在2000下尝试用iasl -d usbhub.sys,为什么不行啊?提示我:invalid table signature found!Table header is not founded!
sfqj82
驱动小牛
驱动小牛
  • 注册日期2004-12-30
  • 最后登录2011-09-03
  • 粉丝0
  • 关注0
  • 积分80分
  • 威望15点
  • 贡献值0点
  • 好评度2点
  • 原创分0分
  • 专家分0分
53楼#
发布于:2005-03-28 16:56
不错,有空的时候研究一下
互相交流~共同进步!
new-oldmen
驱动中牛
驱动中牛
  • 注册日期2002-11-20
  • 最后登录2012-02-06
  • 粉丝1
  • 关注1
  • 积分6分
  • 威望11点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
54楼#
发布于:2005-03-21 11:02
不错,有空的时候研究一下
tiamo
VIP专家组
VIP专家组
  • 注册日期2002-02-26
  • 最后登录2018-01-09
  • 粉丝17
  • 关注4
  • 积分50分
  • 威望142点
  • 贡献值1点
  • 好评度40点
  • 原创分2分
  • 专家分15分
  • 原创先锋奖
  • 社区居民
55楼#
发布于:2005-03-20 02:12
今天要说的是usbhub

usbhub他是一个usb hub device的驱动程序

当kernel枚举到某个usb host controller的时候,usb host controller就会创建一个pdo,这个pdo代表了跟host controller关联到一起的root hub,windows为这个pdo加载的驱动程序就是usbhub.sys...

usbhub致力于usb设备的枚举,pnp管理,电源管理这些事务,而实际的usb的数据信息传输则是由usbport来完成的.所以在usbhub这个驱动程序里面并没有多少usb 2.0跟usb 1.1的差别

usbhub主要完成的功能,也就成了今天这个文章里面最主要的内容

列一个表,讲解的顺序也就如下
1.DriverEntry
2.AddDevice
3.pnp for hub fdo
4.pnp for pdo
5.internal io control dispatch
6.power dispatch

上面的顺序也几乎是一个sys驱动里面的routine所被调用的顺序
重点放在pnp跟power上面
而pnp的部分又被分成了两个一个是for fdo的,一个是for pdo的
重点中的重点是fdo的query bus relationship的处理
而其他的比如start,stop,remove这些只是稍微提一下
而for pdo的部分,query id,query text,query caps这些算是比较重要的处理,但是这些处理的过程都是很显而易见的

至于power部分.遵循windows的通用power处理模型

internal io control则是对外的接口.大部分的usb device的请求都是用过他来进行了
这里简单的列一个表.说明大部分的io control的处理语意.简单流程.完成的功能.这里还要设计到部分usbport的功能

好.进入主题
1.DriverEntry.简单.设置了大多数的dispatch routine,driver unload,add device,然后query了部分reg config,然后为usb的serial number generation作了些准备就return.
至于都设置了哪些dispatch routine
如下
create 简单完成
close 简单完成
device control 面向user mode的那些诸如get node info请求
internal device control 面向kernel mode的请求,下面详细介绍
pnp 下面详细介绍
power 下面详细介绍
system control 交给wmilib完成

至于什么是serial number generation,以及主要致力于在这个generation过程中所分配的资源的释放问题的driver unload routine,会在说到这个serial number的时候交代

2.AddDevice,当系统枚举到一个usb hub的pdo的时候,加载usbhub.sys然后在适当的情况下调用这个函数.
这个函数作的工作很简单
无外乎就是创建一个fdo,然后初始化他的device extension的诸多成员.usbhub同时在这里初始化了wmilib的调用信息.为自己的fdo的wmi注册

3.pnp for fdo
完成了adddevice调用以后,kernel就会开始发送pnp的irp到usbhub,这些irp都是为这个新创建的fdo准备的

前导的query caps这个很简单,设置一个完成routine,然后传递下去
在complete routine里面设置了Surprise Remove OK,这个设置会阻止safe removal hardware对话框以及任务栏图标的出现.特别注意一点,他是为hub本身这个设备而设置的.对于连接到hub上的设备来说,设置还是不设置这个标记的决定权在于那个设备所对应的fdo的驱动程序.(当然还有filter do)

接着是start
在这个irp里面,首先把这个irp传递下去,等待lower driver 完成这个irp,然后hub再完成自己的初始化,诸如获取root hub的pdo,获取device descriptor,获取hub descriptor,获取configuration descriptor,计算自己用于remote wakeup的power state,获取由usbport提供的USBHUB跟USBD两个interface以备将来使用.接着hub 发出一个select configuration的urb,等待完成,保证必要的结果,接着分配一个重复使用的irp(通过在complete routine里面返回more process required),并利用自己的device extension的空间构造一个用于interrupt transfer的urb,同时为这个urb分配必要的输入输出缓冲区.接着hub初始化他的每个port,然后发出这个interrupt urb

题外话.这个urb是用来干什么的?
按照usb的规范.hub除了默认的用于control transfer的endpoint0以外,还有一个用于interrupt transfer的endpoint,这个endpoint用来通知host有一个变化发生到了hub或者hub的某个port或者某些ports上面.
他的输出情况是一个bitmap,这个bitmap的每一个位代表了发生变化的port编号,而第0位代表了hub本身.
比如.如果interrupt transfer返回的数据缓冲里面的2进制值是00101就表示hub本身和端口2(从1开始计算)发生了变化,当host解析这个数据以后就应该对发生变化的hub或者端口发出get status请求来得到具体是发生了什么样子的变化,从而决定应该作什么样的操作

一般的情况下.一个比较智能的hub会用nak回答这个interrupt,直到实际上检测到了变化为止.而不是返回一个全0的bitmap,对于nak的transfer.usb host controller的miniport driver会自动的重试.

这个urb的处理是整个usb fdo处理的核心部分.hub枚举连接到其上的设备也是依靠他来完成的,这个先放一放,下面会有专门的部分讲解.

接下来的irp就不一定有顺序了
我们一个一个的来看吧
query remove
cancel remove
query stop
cancel stop
这些都是为pnp状态变化作准备的
他们都设置了Irp->Status = success
然后传递给下层驱动了

stop
首先检查连接到hub上的device有没有被stop(windows在stop hub的时候会首先发送stop到这些device去),如果没有,就表示这个device的driver没有正确的处理stop这个请求,hub主动的stop掉这些device对应的pdo,接着hub作一系列的cleanup工作.
诸如
cancel掉自己的wait wake power irp,cancel掉自己的idle request irp,用delete pending 完成连接到自己上面的device所发出的wait wake irp以及idle notification irp,abort掉自己在start里面发出的urb,free为这个urb所分配的irp,以及输入输出缓冲区,然后通知usbport 那些为连接到这个hub上的device所创建的device handle应该要被删除了(通过在start的时候所获取的interface完成).disable 掉自己所有的port,释放为自己的device descriptor,hub descriptor,configuration descriptor所分配的空间,然后发出一个设置了null configuration descriptor的select configuration urb来unconfig 这个hub.最后,当然这个irp传递给了下层驱动.

surprise removal
为什么这个放到这么前面来说?因为大多数情况下在remove之前都会遇到他.当某个设备在某次query bus relation的时候没有被返回,kernel就会在恰当的时候发送他到那个消失了的device去,而当这个device open handle变成0(不是pointer count)的时候,kernel再发送remove到这个device..这个部分可以参考ddk的描述

fdo的处理很简单,设置status = success,然后传递下去

remove
跟stop差不多,首先remove掉那些连接到自己上的device并且没有处理remove操作的pdo(所有连接到自己上面的pdo都被放入了一个数组里面,如果处理了remove irp,pdo会把自己从这个数组里面删除,这样fdo只用遍历这个数组就知道哪些pdo还没有处理remove操作了,另外,在创建pdo的时候,也就是在query bus relation的时候,pdo被加入到了这个数组里面).然后hub作如同stop一样的clean操作,因为按照规范,stop并不会作为remove的前导irp发送,所以完全可能一个处于start状态的设备在没有收到stop的情况下收到remove请求,当然hub保留了一个标志在其device extension里面,以避免对一个已经完成了clean操作的设备再作第二次clean操作.
clean完了以后.hub取消fdo的wmi的注册,然后传递remove下去,然后deattch自己,然后delete自己.

关于hub fdo的start,stop,remove就如上.
hub fdo除了处理这些状态变迁的pnp irp以外(当然还有query caps和query relation).还处理了一个pnp irp,就是query device state

hub fdo根据自己保存的当前状态(hub的状态变化会通过上面那个interrupt urb返回)来回应query device state.

下面就是重头戏了.query device relation的处理
正如大多数的bus 的fdo一样.只是处理bus relation

(注:大多数bus的fdo处理bus relation,而bus枚举出来的pdo处理target relation,usbhub也是这样,而其他的relation,据我跟踪到的情况,pci会为有着multi function的device处理remove relation)

先说这个irp会在什么时候被发送
kernel会在start以后发送这个irp,kernel还会在某些特别的情况下发送这个irp,比如当query device state返回了那些错误的值的时候

但是大多数情况是因为某个地方用busRelation作为参数调用了IoInvaliateDeviceRelations函数而引起kernel发送这个irp

usbhub也是一样.这里先回到上面这个那个urb的处理上来

当hub硬件检测到某个port发生了状态变化的时候,这个urb被完成,usbhub设置的complete routine被调用,usbhub读取发生变化的port的状态,发现了是属于一个连接状态变化(connection change),也就是说或者是一个设备被连接了进来,或者是一个设备被拔了下来,usbhub就会调用IoInvalidateDeviceRelations函数.(实际上的处理并不只是port的connection变化处理,还有hub状态变化,以及port的其他状态变化,比如reset完成,resume信号被检测到等等处理,而且按照规范,还会通过control pipe发送clear feature的request来应答硬件所报告的这个变化情况)

hub的fdo收到了这个irp以后,遍历自己的每个port,获取其状态,根据其状态作进一步处理
queryBusRel:
    for(i = 0;i < NumberOfPorts; i ++)
    {
        get port i + 1 status // base is port 1
        if(status & connected)
        {
            if(fdoExt->portData[ i ]->pdo)
            {
                ref the pdo
            }
            else
            {
                reset port i + 1
                create pdo,do some initialization (*)
                ref the new created pdo
                fdoExt->portData[ i ]->pdo = new created pdo
            }
        }
        else
        {
            if(fdoExt->portData[ i ]->pdo)
            {
                pdoExt->ReportMissing = TRUE
                free pdo\'s serial number string
                tell usbport to remove pdo\'s device handle
                disable port i + 1
            }

            fdoExt->portData[ i ]->pdo = 0
            fdoExt->portData[ i ]->connect = notConnected
        }
    }

    pBusRel = ExAllocatePool()
    pBusRel->Count = 0
    for(i = 0; i < NumberOfPorts; i ++)
    {
        if(fdoExt->portData[ i ]->pdo)
           pBusRel->DeviceObject[pBusRel->Count ++] = fdoExt->portData[ i ]->pdo
    }

    pIrp->IoStatus.Information = pBusRel


大致的处理就如上,逻辑上是非常简单的

下面来看上面标记有*的地方的处理.这个是hub为连接到其上的device作的创建初始化工作

createPortDevice:
     generate a name(such as usbpdo-2)
     IoCreateDevice
     Device->StackSize = roothubPdo->StackSize
     use port number to build instance string
     tell usbport to create new device handle
     save the device handle to the pdo\'s extension
     reset port,e.g. reset the device
     tell usbport to init the device(allocate a usb addr)
     get the device,configuration desc for the device

     if the device reports a serial number string
        verify the reported serial number string
     if verify failed or the device didn\'t report
        if serial number string generation isn\'t disabled
            generate a serial number string for the device

     special check the new device is a hub or not
     if the device is a hub
        save the only one interface descriptor to pdoExt


大部分的工作都是由usbport来完成的,主要就是用于usbport维护device handle的数据结构的创建,分配usb address

然后按照需要为这个usb device生成一个serial number string
这个serial number string被用在了两个地方
一个是用来回应Query instance id请求,一个是用来设置caps的UniqueId成员

他是使用一个逐次增加的数字来生成这个string

usbhub为每个设备记录一份数据,里面放置有vendor id跟product id还有device object
00000000 SERIAL_NUMBER_TABLE_ENTRY struc ; (sizeof=0XC)
00000000 idVendor        dd ?
00000004 idProduct       dd ?
00000008 PhysicalDevice  dd ?
0000000C SERIAL_NUMBER_TABLE_ENTRY ends


然后把这些entry组成一个数组
每次生成string的时候都遍历这个数组
查找具有相同的vendor id和product id并且PhysicalDevice非空的元素并计数
直到找到一个PhysicalDevice为空(以前有,然后被删除了)或者整个数组都遍历完了为止

然后用计算出来的数字生成一个Inst %d的string作为他的serial number string
上面这个数组是动态分配增长的.所以在driverunload里面进行释放工作

最后注意一点.新设备的stacksize是跟roothub的pdo的stack size相同的.这是因为发送到pdo的irp要么被完成了(比如create,close,power,wmi,pnp)要么被传递给了root hub pdo(也就是internal io control)

那些被传递给root hub pdo的internal io control不会再往下传递了.所以这里并没有使用rootHubPdo->StackSize + 1这个数字,因为+1是不需要的

注意这个事实.internal io control是直接传递给root hub pdo的
而不是传个fdo,然后再pdo再fdo再pdo这样的方式.如下图
root hub pdo
    |
    +---root hub fdo
           |
           +----hub A pdo
                 |
                 +---hub A fdo
                     |
                     +---device pdo
                          |
                          +--device fdo


一般的讲,urb在device fdo的地方构造,然后用internal io control传递给device pdo,他是由 hub A创建的,device pdo直接把这个irp传给了root hub的pdo,而不是交给hub A fdo,然后再交给hub A pdo.....这个在说到internal io control的处理的时候就会知道原因

接下来要说的就是为这个新创建出来的pdo所发送的pnp irp

4.pnp for port device pdo
同样先是query caps
rawDevice = 0,removable = 1
如果serial number generation is enabled,uniqueId = 1
如果wakeup is supported
DeviceD1 = DeviceD2 = WakeFromD0 = WakeFromD1 = WakeFromD2 = 1

然后根据fdo的caps在start的时候所计算的device power state map设置pdo的caps里面的device power state map

最后完成这个irp

start port pdo
创建device interface,如果是hub则是_GUID_DEVINTERFACE_USB_HUB否则则是_GUID_DEVINTERFACE_USB_DEVICE,然后按照需求进行restore(多发生在stop了以后再start的情况,会重新通知usbport创建device handle(stop会remove掉这个device handle),还会进行reset),然后注册pdo的wmi,最后完成这个irp

query remove
cancel remove
query stop
cancel stop
直接用success完成irp

stop
complete wait wake irp,complete idle notification request irp,告诉usbport remove 这个pdo的device hanlde(start会重新创建这个device handle).然后disable掉pdo连接的port,然后删除start的时候注册的device interface,最后完成这个irp

surprise removal
删除device interface然后完成这个irp

remove
complete wait wake irp,complete idle notification irp,删除device interface.disable port,remove device handle,清除掉fdoExt->portData数组里对应的pdo指针和connect成员
释放掉serial number的内存,取消pdo wmi的注册,最后delete device,完成irp

query device relation
target relation,简单到不能再简单

query interface
因为usbport对于_USB_BUS_INTERFACE_USBDI_GUID这个interface要求必须设置interface结构的InterfaceSpecificData为device handle
所以usbhub设置了这个成员然后传递给root hub pdo
这里是第一次看到usbhub为某个request设置device handle,后面还会看到internal io control的request也会设置device handle成员.正是因为usbhub无条件的设置这个device handle,所以irp直接传递给root hub的pdo的,一来能节省不必要的传递过程,二来因为usbhub这种强制的设置功能.如果传递给fdo再pdo的话,device handle总是被usbhub用pdo的device handle修改,这样device handle成员就发生了变化.这是不允许的

query bus information
返回 GUID_BUS_TYPE_USB

query resource requirement
这个irp的处理有点奇怪.
都知道usb设备是不需要硬件资源的.usbhub的处理方式只是设置了一个标记.因为kernel在为pdo创建device key的时候会发送一系列的irp到pdo.只有在这些irp都成功了以后,kernel才会创建或者更新device key,这个irp就是其中一个.从源代码上来看.他并不算是最后一个,但是确是这一系列的irp中只发送一次的一个.只有在这个irp发送完成了以后.诸如IoOpenDeviceRegistryKey这些函数获取到的信息才真实的反应了当前这个设备的信息.正如你想的那样.有地方要依靠他们的存在.

query device state
如果fdo的处理一样,根据pdo的状态回应这个query irp

query device text
用irp里面的LCID作为language id,然后用device descriptor里面的iProduct作为string index发送get string descriptor request到usb设备,如果成功了则返回,否则使用使用English (United States) = 409h作为language id再次get string descriptor,如果还是失败,则返回generic usb string,他是在driverEntry里面从注册表里面读出来的

query device id
这个处理过程就没有任何好多说的了,也就是用那些idVendor,idProduct生成各种id,利用device tree可以查看到usbhub为pdo生成的各种id string

pnp的处理就到这里
接下来的是internal device control的处理
这个也是usb设备的接口

5.internal device control
再次提到device handle这个东西,传递给usbport的urb必须要设置URB_HEADER->UsbdDeviceHandle成员,很明显这个是由usbhub创建的pdo来完成的.正如上面看到的那样,pdo在通知usbport创建device handle的时候保存这个device handle到了device extension里面,所以pdo会从自己的device extension里面取得这个device handle并用他来设置URB_HEADER里面得成员.这也是为什么urb只是经过一个pdo以后就交给了root hub pdo.因为如果交给了另外一个hub创建得pdo的话,新的pdo会覆盖原来或许本来已经设置正确了的device handle,这当然不行了

也正是这个原因.internal device control最先被处理的应该是hub创建的pdo,而不应该传递给hub的fdo,所以下面的都是pdo的处理

这里列个表,来描述各个internal device control的处理情况

1.submit urb 220003h
usbhub首先根据当前pdo的状态(是否已经start,是否是位于d0状态,等等)适当的failed掉这些状态的urb.如果当前的pdo状态是能够接受urb的状态,则继续

然后检查URB_HEADER->Function对几个特别的function作额外的处理
这些要特别处理的Function是
select configuration
select interface
control transfer
interrupt transfer
iso transfer
get ms feature descriptor

中间的3个transfer会检查ReportMissing状态(查看上面QueryBusRelation的处理),适当的fail这个urb

最后一个ms feature descriptor则完全是有usbhub来完成的.详细的信息可以看这个网页http://www.sourcequest.com/modsContent.htm

对于select configuration的情况.如果是config device,则要对configuration descriptor作一次validate,具体也就是检查他的bLength,bDescriptorType,wTotalLength作一次检查,然后检查在configuration descriptor设置的MaxPower是否大于hub能提供的PerPortPower,如果也通过了,则设置一个complete routine,然后传递给root hub pdo

对于select interface也设置了相同的complete routine

在这个complete routine里面主要是处理带宽不够的情况.设置一个timer.用来记录这个wmi事件.使用这个timer是基于这样的事实:客户端的驱动在遇到select configuration和select interface返回带宽不够的时候重试urb,因为如果在重试成功了以后就会cancel这个timer.

最终这个urb被传递给了root hub pdo,交由usbport进行排队,传输,完成..

2.reset port 220007h
这个的语意是reset device所连接到hub对应的port,也就是说是用来reset device的upstream port.实际上就是发送reset信号到device去.

主要的port reset是由usbhub处理,而reset port也必须reset 由usbport管理的device handle.流程如下
resetUpStreamPort:
    tell usbport to mark device handle busy
    tell usbport to remove device handle but keep the struct data 
    send reset request to hub hardware
    tell usbport to create new device handle
    send reset request for set usb address
    tell usbport to allocate a new address for the device
    tell usbport to use the new created device handle to reinitialize old device handle
    tell usbport to remove the new created device handle


3.get root hub pdo 22000fh
直接交给root hub pdo,root hub pdo会返回两个指针,虽然msdn说一个是root hub pdo,另外一个是host controller fdo,但是实际上两个都是指向了root hub pdo,不过这个地方很奇怪,hub已经保存有这两个指针,为什么不用自己保存的指针填充这个结果而要交给root hub处理呢?

4.get port status 220013h
usbhub处理,发送control urb到hub硬件获得这个port status,然后返回

5.enable port 220017h
usbhub处理,同上

6.get hub count 22001bh
usbhub,root hub都处理,usbhub 先把count += 1然后另外发送一个get hub count到自己的下层驱动,把返回的结果加在count上,返回,为什么要新创建一个urb下传呢?因为原先的irp的stack size的数目不够.

7.get hub name 220020h
usbhub处理,返回hub的symlink name.

8.get bus info 220420h
通过interface的方式交由usbport处理完成

9.get controll name 220424h
同上

10.get parent hub info 22042ch
usbhub pdo返回自己的parent fdo对应的pdo,以及自己的port number

11.get device handle 220423h
usbhub pdo返回保存在自己device extension里面的device handle

12.cycle port 22001Fh
模拟一次unplug and plug event

设置fdoExt->portData[portNumber]->pdo
然后IoInvalidateDeviceRelations.
对比上面的代码就知道会新创建一个pdo,而kernel会发生surprise removal以及remove pnp irp到原先的那个pdo完成删除工作

13.notification 220027h
这个稍微有些复杂,因为他是和power management关联在一起的
说完power management再回头来看他的处理过程

6.power dispatch
这个部分处理很有模版化的趋势
先看fdo的处理

query power
FdoQueryPower
   if(bQuerySystemPower)
   {
       newDeviceState = map system state to device state
   }

   if(newDeviceState == D3)
   {
       if(fdo had submitted wait wake irp)
           cancel it // because we are going to d3
   }

   PoStartNextPowerIrp
   PoCallDriver


wait wake
FdoWaitWake:
    set complete routine = FdoWaitWakeComplete
    PoStartNextPowerIrp
    PoCallDriver


wait wake complete
FdoWaitWakeComplete:
    if(irp->IoStatus->Status == STATUS_SUCCESS)
    {
        request power irp to set device state to D0
        // in the complete routine,complete children port pdo\'s
        // wait wake irp with STATUS_SUCCESS
    }
    else
    {
        complete all children port pdo\'s wait wake irp with
        error
    }


set fdo system power state
FdoSetSystemPowerState:
    if(newSystemState == S0)
    {
        if(currentDeviceState != D0)
        {
            save this irp
            Request set device power d0 Irp
            return pending

            // in the complete routine,call down the saved set system power irp
        }
        start next
        call down
    }
    else
    {
        if(some one need wait wake)
        // hub or device that connected to the hub
        {
            newDeviceState =map system state to device state
        }
        else
            newDeviceState = D3

        if(prevDeviceState == newDeviceState)
        // didn\'t need to change device state
        {
            start next power irp
            call down
        }
        else
        {
            save the set system power irp
            request set device power state irp
            return pending
            // in the complete routine,call down the
            // saved set system power irp
        }
    }


fdo set device power irp
FdoSetDevicePowerIrp:
    if(newState == prevState)
    {
        start next
        call down
        return
    }

    // is not the same
    if(newState != D0)
    {
        cancel interrupt transfer
    }

    set complete routine = FdoSetDeviceStateComplete
    call down


set fdo device state complete
FdoSetDeviceStateComplete:
    if(irp->IoStatus.Status != STATUS_SUCCESS)
    {
        start next
        return success
    }

    if(newState == D0)
    {
        if(prevState == D3)
        {
            query work item = FdoSetPowerD0Worker
            save this irp
            return more_process_required
        }

        submit interrupt transfer
        start next
    }

    return success


fdo set power d0 worker
FdoSetPowerD0Worker:
    power on all ports
    flush changes when the hub was powered off
    invalidate bus relation
    submit interrupt transfer
    start next
    complete this power irp


pdo的power处理相对简单很多

pdo power dispatch

PdoPowerDispatch:
   if(minor == WAIT_WAKE)
   {
       return PdoWaitWake
   }

    if(pdoExt->ParentFdoExt->CurrentDevicePowerState != d0)
    {
        if(minor == set || minor == query)
        {
            request set parent hub d0
            // complete = PdoSetHubD0ForPowerProcess
            return pending
        }
    }

    switch(minor)
    {
    case query
        return PdoQueryPower
    case set
        return PdoSetPower
    default
        status = pIrp->IoStatus.Status
        start next
        complete
        break
    }

    return status


PdoSetHubD0ForPowerProcess:
    switch(minor)
    {
    case query
        PdoQueryPower
    case set
        PdoSetPower
    default
        start next
        complete
        break
    }

    return success


pdo wait wake
PdoWaitWake:
    set cancel routine
    save this irp
    if(!pdoExt->ParentFdo->WaitWakeIrpSubmitted)
    {
       call PoRequestPowerIrp to request a wait wake power irp  to the ParentFdo stack
       start next
       return pending
    }


pdo query power
PdoQueryPower:
    if(bQuery == query device state)
    {
        start next
        return SUCCESS
    }

    newDeviceState = map system state to device state

    if(newDeviceState != D3)
    {
        start next
        return success
    }

    // here new device state = D3
    if(!pdoExt->WaitWakeIrp)
    {
       start next
       return success
    }

    // here new state == D3,and has a wait wake irp pending
    if(newSystemState >= PowerSystemHibernate)
    {
        start next
        return success
    }

    // device new state is d3,this state can\'t wake system
    // and the system go to a wakable state
    // so fail it
    return STATUS_UNSUCCESSFUL


pdo set power state
PdoSetPowerState:
    if(bSetSystemPowerState)
    {
        start next
        complete success
        return success
    }

    if(newState == prevState)
    {
        start next
        complete success
        return success
    }

    switch(newState)
    {
    case D0:
        if(prevState == D3)
        {
            if(pdo is suspended)
                resume it
            else if(pdo is power down)
                power it
           
            check port status
            if(status & connected)
            {
                tell usbport to reinit device handle
            }
        }
        else
        {
            send clear suspend request
            complete idle request
            clear remote wakeup feature
        }
        break

    case D1
    case D2
        if(prevState == D1 || prevState == D2)
        {
            start next
            complete success
            return success
        }

        if(pdoExt->WaitWakeIrp)
        {
            // set hardware to support remote wake
            enable port
            set remote wakeup feature
        }

        tell hardware to suspend port
        break

    case D3
        complete idle notification with status = POWER_STATE_INVALID
        complete wait wake with status = POWER_STATE_INVALID
        if no one request wait wake
            cancel parent hub\'s wait wake irp

        close port
        break
    }


    start next
    complete success
    return success


处理的方式都是显而易见的.遵循ms的power处理的规范
没有什么特别注意的地方.查看msdn就能找到这个处理流程的描述

唯一一个让我觉得奇怪的是
他并没有按照msdn所描述的那样调用PoSetPowerState函数
而且根据我跟踪分析的大多数驱动
比如pci.sys,usbstor.sys,hidclass.sys,usbhub.sys这些都没有调用PoSetPowerState函数...令我觉得很是奇怪

再回头来看idle notification这个request
至于这个idle request是用来干什么的,改设置什么参数.参考msdn

usbhub对这个的处理方式很直接
所以发送到pdo的idle notification request都设置一个cancel routine然后保存起来
在某些地方检查整个hub是否进入了一个idle状态,if and only if所有的port上的设备都发送了idle notification的irp的时候,hub进入了idle状态,于是hub 回调每个port所提供的回调函数,然后往自己的parent hub发送一个idle notification request

而这个idle notification irp的完成.当然是在hub自己的idle request完成的时候一一完成自己的每个port上的idle request

usbhub的就到这里...
打字也是一件痛苦的事情
MichaelLi
驱动牛犊
驱动牛犊
  • 注册日期2003-10-02
  • 最后登录2009-02-10
  • 粉丝0
  • 关注0
  • 积分700分
  • 威望70点
  • 贡献值0点
  • 好评度70点
  • 原创分0分
  • 专家分0分
56楼#
发布于:2005-03-09 21:33
强!!
qwdrv
驱动大牛
驱动大牛
  • 注册日期2004-03-19
  • 最后登录2005-12-15
  • 粉丝0
  • 关注0
  • 积分8分
  • 威望2点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
57楼#
发布于:2005-03-09 10:41
好东西
bmyyyud
驱动老牛
驱动老牛
  • 注册日期2002-02-22
  • 最后登录2010-01-21
  • 粉丝0
  • 关注0
  • 积分1000分
  • 威望130点
  • 贡献值0点
  • 好评度106点
  • 原创分0分
  • 专家分0分
58楼#
发布于:2005-03-07 16:39
必看!!
滚滚长江东逝水 浪花淘尽英雄 是非成败转头空 青山依旧在 几度夕阳红 白发渔樵江渚上 惯看秋月春风 一壶浊酒喜相逢 古今多少事 尽付笑谈中
tiamo
VIP专家组
VIP专家组
  • 注册日期2002-02-26
  • 最后登录2018-01-09
  • 粉丝17
  • 关注4
  • 积分50分
  • 威望142点
  • 贡献值1点
  • 好评度40点
  • 原创分2分
  • 专家分15分
  • 原创先锋奖
  • 社区居民
59楼#
发布于:2005-03-07 16:25
今天进入重头了..usbport.sys跟踪分析..

usbport是一个usb host controller的port driver.
而usbuhci是uhci类型的usb host controller的miniport driver
usbehci则是ehci类型的usb host controller的miniport driver
usbport负责创建管理usb host controller的fdo
那么对应的pdo是由谁创建管理的呢?
当然是这个host controller所在的总线的driver创建管理的了
当前的架构上这个bus一般是pci.

usbport主要完成的功能如下
1.抽象层次上管理usb host controller,实际的操作是由各个miniport driver 完成的.
2.创建并管理集成在host controller里面的root hub的pdo.这个pdo上会attach 由usbhub.sys创建并管理的fdo.
3.管理维护所有挂在当前这个host controller上的最多127个usb 设备.
4.处理大多数针对当前这个host controller管理下的bus上的所有usb设备所发出的usb request blocks(URB).

基本功能如上
这里的文字也主要针对上面这些功能一一描述
其中
1.这个是集成到各个其他操作里面的.通用的操作由usbport完成.特定了和硬件相关的操作通过调用各个miniport所注册的callback routine来完成.usb host controller的miniport跟其他各种的miniport driver一样.在driverentry里面填写好一个结构.调用usbport的一个导出函数.usbport修改这些结构的某些成员提供自己实现的一些函数指针以供miniport driver调用.
struct _USB_MINIPORT_REGISTRATION_PACKET
{
    ULONG    ulVersion;
    ULONG    ulFlags;
    ULONG    ulTotalBandWidth;
    // some other fields
    // .....
    // miniport\'s routines
    // ............
    // output service routines
    // filled by usbport.sys
    // .........
};


2.root hub的pdo创建很简单.不外乎就是在query bus relationship的是创建一个device并且返回就行了.至于关联到这个root hub pdo上的操作,大部分都交给了usb host controller fdo来完成了.马上就能看到其实大部分的操作都是发送给这个pdo的.

3.跟4.就是今天的重点部分了.这两个部分也是usbport.sys里面着墨最多的地方.我讲的顺序也是先说第三部分.再说第四部分.但是这两个部分并不是完全分开的.比如设备管理的时候可能涉及到要向某个设备发送一些urb.最典型的就是要set address.这里当然就必须由第四个部分来完成了.而且urb的处理也极大的依赖管理设备的时候所建立和维护的诸多数据结构.但是为了讲解方便.在说到第三个部分的时候总是假设usbport有某种方法能把某个urb发送出去并且完成.暂时不管他具体是怎么完成的.

首先是设备管理
在前面说到usbport实现了两个interface以提供usbhub.sys调用,并在usbhub的帮助下来收集所有连接到总线上的设备信息.这里我们跳过这两个interface的具体说明以及调用方式调用时间等等信息,等到讲到usbhub的时候再回头来看这个过程.那个时候再来说究竟在什么时候在什么样子的情况下usbhub会调用哪个函数.如果没有调用上下文要去分析一个函数的功能是比较盲目的.这里大家了解这样一个规则.usbhub用某种方式得知了总线上有个设备连接进来了(比如你插入了一个u盘).usbhub通过调用USBPORTBUSIF_CreateUsbDevice.通知usbport这个事件发生了.(函数参数信息可以查阅ddk).下面得分析也就是从这个地方开始的.

在经过必要的参数检查测试以后,usbport为这个新报告的设备分配一个结构,用来保存必要的信息
00000000 DEVICE_HANDLE   struc ; (sizeof=0X70)
00000000 Sig             dd ?    // signature = \'HveD\'
00000004 DeviceAddress   dw ?    // usb device addr (1-127)
00000006 PortNumber      dw ?    // port number
00000008 RefCount        dd ?    // reference count
0000000C Tt              dd ?    // for usb2.0 
00000010 ParentHandle    dd ?    // parent hub device handle
00000014 ConfigHandle    dd ?    // CONFIG_HANDLE*
00000018 PipeHandle      PIPE_HANDLE ? // for default pipe
00000038 DeviceSpeed     dd ?    // see ddk
0000003C DeviceDesc      USB_DEVICE_DESCRIPTOR ?
0000004E                 db ?    // padding,alignment
0000004F                 db ?    // padding,alignment
00000050 DeviceFlags     dd ?    // flags
00000054 DeviceHandleListEntry LIST_ENTRY ? // link all device handle together
0000005C PipeHandleListHead LIST_ENTRY ? // pipe handle list header
00000064 TtCount         dd ?    // for usb2.0
00000068 TtListHead      LIST_ENTRY ? // for usb2.0
00000070 DEVICE_HANDLE   ends


usbport设置好某些域以后开始open the default pipe,完成以后,发送一个get device descriptor的urb到这个新出现的usb设备用来填写DeviceDesc成员.成功以后把这个新创建的device handle添加到由usb host controller所维护的device handle list上去

createdevice(parenthandle,portnumber,portstatus):
    check parameters
    allocate buffer for device handle struct
    handle->refcount = 0
    handle->confighandle = 0
    handle->parenthandle = parenthandle
    handle->portnumber = portnumber
    handle->deviceaddress = 0 // default usb address
    according to portstatus,set handle->devicespeed
    according to devicespeed,set default pipe\'s maxpacketsize
    init other pipe_handle\'s member for the default pipe
    open the default pipe\'s endpoint
    get the device descriptor
    link the device handle to the fdo\'s device handle list header


在看open default pipe之前得先看看pipe handle这个结构
00000000 PIPE_HANDLE     struc ; (sizeof=0X20)
00000000 Sig             dd ?    // signature = \'HpiP\'
00000004 EndPointDesc    USB_ENDPOINT_DESCRIPTOR ?
0000000B                 db ?    // padding,alignment
0000000C Flags1          db ?    // flags
0000000D Flags2          db ?
0000000E Flags3          db ?
0000000F Flags4          db ?
00000010 field_10        dd ?    // unknown
00000014 EndpointPointer dd ?    // ENDPOINT*
00000018 ListEntry       LIST_ENTRY ? // link pipe handle to device handle\'s PipeHandleListHead member
00000020 PIPE_HANDLE     ends


很简单的一个结构
现在来看open endpoint这个函数
先照样作一些检查.然后为endpoint结构分配内存.因为这个endpoint结构不仅仅是usbport使用.miniport也会使用.所有这个结构的大小是不固定的.miniport在注册自己的时候会指定自己所需要的结构大小,usbport在这个大小的基础上加上自己所管理的大小来决定要分配多大的内存.
然后usbport作很多的初始化工作.设置大部分endpoint结构的成员.必要的时候还需要miniport的帮助.
完成以后把这个pipe handle连接到device的pipe handle list header上面.同时把新创建的endpoint也连接到由fdo维护的全局endpoint list header上面去

endpoint是一个很大的结构,0x160个字节(checked xp版本),篇幅原因不一一列举了.看几个与我们分析相关的成员
00000000 ENDPOINT        struc ; (sizeof=0X160)
00000000 Sig             dd ?    // \'PEch\'
00000010 FunctionDevice  dd ?     // host controller fdo
00000024 DeviceHandle    dd ?     // device handle
00000030 CurrentState    dd ?     // endpoint state
00000034 NextState       dd ?     // those two field will be protected by StateLock(see below)
00000038 FrameNumber32Bit dd ?    // for iso,current frame number
0000003C WorkerRoutine   dd ?     // core work routine
00000040 ActiveTransfer  LIST_ENTRY ? // activer transfer list header
00000048 PendingTransfer LIST_ENTRY ? // pending transfer list header
00000050 CancelTransferList LIST_ENTRY ? // cancel transfer list header
00000058 AbortIrpList    LIST_ENTRY ? // abort irp list header
00000060 EndpointListEntry LIST_ENTRY ? // link all endpoints together to fdo\'s list header
00000068 AttendLink      LIST_ENTRY ?  // see below
00000070 StateChangeListEntry LIST_ENTRY ? // endpoint state change list entry linked to fdo\'s list header
00000078 ClosedLink      LIST_ENTRY ? // for lazy close,linked to fdo\'s list header
00000098 FlushList       LIST_ENTRY ? // flush list entry,linked to fdo
000000B8 ResourceLock    USB_LOCK ?   // spin lock for this struct
000000D4 StateLock       USB_LOCK ?   // spin lock for endpoint\'s state
000000F0 SavedResIrql    db ?    // save irpl
000000F1 SavedStateIrql  db ?
000000F8 CommonBuffer    dd ?    // for miniport driver COMMON_BUFFER*
000000FC DeviceAddress   dw ?    // device addr,copied from device handle
000000FE EndPointAddr    dw ?    // endpoint number
00000100 MaxPacketSize   dw ?    // max packet size
00000102 Period          db ?    // period
00000104 DeviceSpeed     dd ?    // device speed
00000108 BandWidth       dd ?    // bandwidth
0000010C Offset          dd ?    // schedule offset
00000110 TransferType    dd ?    // transfer type
00000114 Direction       dd ?    // in or out
00000118 CommonBufferVir dd ?    // for common buffer
0000011C CommonBufferPhy dd ?
00000120 CommonBufferLen dd ?
00000128 MaxTransferLen  dd ?
0000012E PortNumber      dw ?
0000013C ClientContextPointer dd ? // point to miniport endpoint context
00000160 ENDPOINT        ends


许多成员的作用就像他的名字所描述的一样
几个特别提出来要注意的地方
首先是state.有两个成员表征了endpoint的state信息.
state是用来描述当前endpoint的状态的.在不同的状态下面对于发送到endpoint的urb的处理方式是不一样.这个会在后面看到

然后是诸多的list entry,他们大多数是用来作urb的排队的.后面也会一一看到用法

workroutine是一个函数指针.他的存在是因为发送到root hub的urb必须特殊处理.而不用转换成usb信号出现在usb总线上面.所有root hub对应的两个endpoint的workroutine是特别设计的.其他的endpoint对应的workroutine是一个通用的函数.
这个workroutine要作的工作就是处理urb

至于那个common buffer,多数情况下是不是用来作传输用的,因为usbport所使用的dma是一个master而且还能scatter-gather的..所以大部分的urb都直接使用他自己的buffer本身占用的物理页,而不需要一个额外的copy操作.只有在某个buffer很不幸的跨越了两个不连续的物理页的时候才需要使用到这个common buffer作额外的copy操作.特别注意这个common buffer是提供给miniport driver使用的.而不是由usbport来使用的..miniport driver还会从这个common buffer里面提交一些内存来作额外的维护信息.

create device就到这里.基本操作就是分配device handle结构.分配endpoint zero 的结构.链接他们到各自对应的list header上面去

device创建好了以后.usbhub再发出另外一个initialize device的请求.对于usbport来说这个请求唯一的处理就是为其分配一个地址,并且发出set address的urb

完成这两步,这个device就能交付使用了.
使用的时候第一个步骤就是select configuration
device的驱动程序读取device的configuration descriptor然后选择适当的interface构造一个select configuration的urb传递下来.中间的处理由usbhub完成,暂时忽略,假定这个urb到达了root hub的pdo.经过一系列的检查(这个部分会在urb的处理的时候再次提到).最终来到处理函数_USBPORT_SelectConfiguration

首先为其分配一个config handle的结构
00000000 CONFIG_HANDLE   struc ; (sizeof=0X14)
00000000 Sig             dd ?    // \'HgfC\'
00000004 ConfigDescPointer dd ?  // ptr config desc buffer 
00000008 InterfaceListHead LIST_ENTRY ? // link interfaces together
00000010 ConfigDesc      dd ?   //  USB_CONFIGURATION_DESCRIPTOR buffer
00000014 CONFIG_HANDLE   ends


很简单的一个变长结构(因为configurtation descriptor是变长的),实际上的configuration descriptor就跟在这个结构的末尾,从0ffset 0x10开始.

当然.device handle结构的config handle成员指向了这个新分配的内存.

接下来发送一个set configuration的urb到设备.
然后一一open 这个select configuration里面所描述的interface.并且填写将来要返回的USBD_INTERFACE_INFORMATION结构

填写information结构是一个很简单的操作,所有必须的信息都在前面的步骤里面完成了.我们只是看看open interface完成的操作

首先还是要分配结构
00000000 INTERFACE_HANDLE struc ; (sizeof=0X3C)
00000000 Sig             dd ?   // \'HxfI\'
00000004 ListEntry       LIST_ENTRY ? // linked to config handle\'s interface list header
0000000C HasAltSetting   dd ?
00000010 InterfaceDesc   USB_INTERFACE_DESCRIPTOR ?
00000019 Padding         db 3 dup(?)
0000001C PipeHandle      PIPE_HANDLE ?
0000003C INTERFACE_HANDLE ends


这个结构是变长的.结尾由若干个pile handle结构.个数当然由interface desc里面的endpointnumber指出

结构分配完了..照例对各个成员进行初始化
然后调用open endpoint函数(如上所述)

最后再把这些个interface handle连接到config handle上面.工作就完成了...

客户驱动程序在这个select configuration urb返回的时候就能查看USBD_INTERFACE_INFORMATION结构一一了解各个自己感兴趣的东西.当然其中最最主要的就是所返回的pipe handle.正如你所想象的那样.他就是一个指向PIPE_HANDLE结构的指针.

客户驱动保存这个指针,因为随后的很多urb都需要你去填写这个pipe handle成员.usbport也需要使用这个指针去寻找对应的endpoint结构.

select configuration完成以后,就能开始传输数据了,这也就是下来的内容了.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里面恰当的填写这个值

上面的这些若干个连接连接就是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作一些检查.然后转到特定的处理函数.先看这个数据结构的定义
00000000 URB_DISPATCH_TABLE struc ; (sizeof=0X14)
00000000 DispatchRoutine dd ?   // process routine
00000004 TransferLen     dw ?
00000006                 db ?   // padding
00000007                 db ? 
00000008 bmRequestDirection db ? // setup packet
00000009 bmRequestType   db ?
0000000A Recipient       db ?
0000000B bRequest        db ?
0000000C Flags           db ?
0000000D                 db ? 
0000000E                 db ? 
0000000F                 db ? 
00000010 FunctionCode    dd ?
00000014 URB_DISPATCH_TABLE ends


很简单的一个结构.TransferLen用于固定大小的传输,用来检查说提交的buffer大小是否正确,如果是0,则不检查大小信息
接下来的几个成员用于control transfer用来填充setup packet
许多的urb都不需要你完全的填充所有的setup packet成员.usbport在这里会为你填充这些已知的固定的成员

flags则是控制usbport的操作方式的.下面会详细的解释
function code则是指明这份数据是对应哪个function的
理所当然的,usbport为每个function code准备一个这样的结构构成一个数组.

processurb(pUrb)
    check function code
    validate 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 也是一个不小的结构
00000000 TRANSFER        struc ; (sizeof=0XC0)
00000010 Direction       dd ?
00000014 TimeoutInterval dd ?
00000018 SubmitTime      LARGE_INTEGER ?
00000024 TransferedLen   dd ?
00000028 Status          dd ?
0000002C Irp             dd ?
00000030 pEvent          dd ?
00000034 UrbPointer      dd ?
00000038 TransferListEntry LIST_ENTRY ?  // link entry
00000044 MappedRegisterBase dd ?   // for dma
00000048 NumberOfMappedRegister dd ?
0000004C TransferDirection dd ?
00000050 TransferBufferLen dd ?
00000060 SetupPacket     URB_SETUP_PACKET ?
00000068 TransferMdl     dd ?
00000070 AdapterDBList   LIST_ENTRY ? // for double buffer
00000078 ParentPointer   dd ?     // split 
0000007C EndpointPointer dd ?
00000080 ClientTransferPointer dd ? // passed to miniport
00000084 ChildTransferListHead LIST_ENTRY ?
0000008C SplitTransferListEntry dd ?
00000094 IsoTransferInfo dd ?  // iso
00000098 sgList          SG_LIST ? // scatter-gather list
000000C0 TRANSFER        ends


留下了一些分析相关的成员
其中如果这个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.

_USBPORT_MapTransfer是个比较复杂的函数了.他涉及到transfer的切割,sgList结构的填写,少安毋躁..哈哈

00000000 SG_LIST         struc ; (sizeof=0X28)
00000000 Flags           dd ?
00000004 CurrentVa       dd ?
00000008 MappedSysAddress dd ?
0000000C EntriesCount    dd ?
00000010 Entries         SG_ENTRY ? // var size
00000028 SG_LIST         ends

00000000 SG_ENTRY        struc ; (sizeof=0X18)
00000000 PhysicalAddr    ULARGE_INTEGER ?
00000008 VirtualAddr     dd ?
0000000C Length          dd ?
00000010 Offset          dd ?
00000014 field_14        dd ? // unknown
00000018 SG_ENTRY        ends

_USBPORT_MapTransfer(pFdo,pIrp,pMapRegisterBase,pTransfer)
    pFdoExt->DoMapping = FALSE
    // 1. build sgList
    ULONG ulMappedLength = pUrb->TransferBufferLength
    do
    {
        IoMapTransfer(...&ulMappedLength..);
        add physical:virtual to sgList
        ulMappedLength = pUrb->TransferBufferLength - ulMappedLength
     }while(ulMappedLength != 0);
    
    // 2. split transfer
    if this is an iso transfer
        init some field for iso transfer only

    LIST_ENTRY listHeader;
    call _USBPORT_SplitTransfer(..pTransfer,&listHeader);
    while(listHeader is not empty)
    {
        get transfer from list header
        insert to endpoint\'s ActiveTransfer
    }

    // 3. endpoint worker
    call _USBPORT_CoreEndpointWorker
    if return != 0
        call _USBPORT_InvalidateEndpoint


看到这里我想你也能大致CoreEndpointWorker跟InvalidateEndpoint的大致作用.很明显是用来处理active transfer list的

先不管他,来看split
USBPORT_SplitTransfer(pFdoExt,pEndpoint,pTransfer,pListHead)
    init pTransfer->ChildTransferListHead
    if(pTransfer->TransferBufferLength <= pEndpoint->MaxTransferLength)
    {
        insert pTransfer to pListHead
    }
    else
    {
        ASSERT(pTransfer->Type == interrupt ||
               pTransfer->Type == bulk)

        call _USBPORT_SplitBulkInterruptTransfer
    }


很简单的逻辑..只有在transfer length 大于endpoint的最大传输大小并且是interrupt或者bulk的时候才split.
最多传输长度并不是MaxPacketSize,这个长度要大得多.

_USBPORT_SplitBulkInterruptTransfer(pFdo,pEndpoint,pTransfer,pListHead)
    ULONG ulChildrenCount = pEndpoint->MaxTransferLen / pTransfer->TransferBufferLength

    ExAllocatePool(sizeof(TRANSFER) * ulChildrenCount)
    copy from pTransfer to all the new allocated structs
    reinit all the children transfer\'s list entry
    
    ULONG ulLeftTransferLen = pTransfer->TransferBufferLength
    pChildTransfer = FirstChildTransfer
    pOrgSgList = &pTransfer->sgList
    curSgEntryIndex = 0
    curOffsetInEntry = 0
    OffsetInTotalBuffer = 0
    while(leftTransferLen != 0)
    {
        call _USBPORT_MakeSplitTransfer(pFdo,pOrgSgList,pChildTransfer,MaxTransferLength,MaxPacketLength,&curSgEntryIndex,&curOffsetInEntry,leftTransferLength,OffsetInTotalBuffer)

        leftTransferLen = return value
        link pChildTransfer to pListHead
        OffsetInTotalBuffer += pChildTransfer->TransferBufferLength
        pChildTransfer = NextChildTransfer
    }


这个过程有些复杂
简单的讲
这个函数首先计算需要多少个children transfer
然后分配这么多个结构.然后把原来结构的内容copy到新的每个结构里面,但是那些list必须要重新初始化成正确的值.不能指向原来的transfer

然后调用一个辅助函数切割原来transfer的sgList.每个小的transfer的大小最多是pEndpoint->MaxTransfer这么大

sgList是由一个一个的sgEntry组成的.这些sgEntry的长度却未必就是pEndpoint->MaxTransfer这么大.所以必须由两变量来表示当前处理的是哪个entry,以及当前处理到了这个entry的哪个offset
也就是上面curSgEntryIndex和curOffsetInEntry的作用,USBPORT_MakeSplitTransfer辅助函数会修改这两个值

最后注意.原来那个被切割的transfer并没有进入pListHeader
当然也就没有进入Endpoint的ActiveTransfer

USBPORT_MakeSplitTransfer(pFdo,pOrgSgList,pChildTransfer,MaxTransferLen,MaxPacketLen,pCurEntryIndex,pCurOffsetInEntry,LeftTransferLen,OffsetInTotalBuffer)
     ASSERT(MaxTransferLen % MaxPacketLen == 0)

     length = MaxTransferLen
     left = pOrgSgList->sgEntry[*pCurEntryIndex].Length - *pCurOffsetInEntry
     pChildTransfer->sgList.Count = 0
     i = 0
     while(length > left)
     {
         pChildTransfer->sgList.Count ++
         pChildTransfer->sgList.sgEntry[ i ]. Length = left
         pChildTransfer->sgList.sgEntry[ i ].VirtualAddr = pOrgSgList->sgEntry[*pCurEntryIndex].VirtualAddr + *pCurOffsetInEntry
         pChildTransfer->sgList.sgEntry[ i ].PhysicalAddr = pOrgSgList->sgEntry[*pCurEntryIndex].PhysicalAddr + *pCurOffsetInEntry
         i ++
         *pCurEntryIndex ++
         *pCurOffsetInEntry = 0
         LeftTransferLen -= left
         length -= left
         left = pOrgSgList->sgEntry[*pCurEntryIndex].Length
     }

     if(length)
     {
         pChildTransfer->sgList.Count ++
         pChildTransfer->sgList.sgEntry[ i ].Length = length
         pChildTransfer->sgList.sgEntry[ i ].VirtualAddr = pOrgSgList->sgEntry[*pCurEntryIndex].VirtualAddr + *pCurOffsetInEntry;
         pChildTransfer->sgList.sgEntry[ i ].PhysicalAddr = pOrgSgList->sgEntry[*pCurEntryIndex].PhysicalAddr + *pCurOffsetInEntry;
         *pCurOffsetInEntry += length
         if(*pCurOffsetInEntry == pOrgSgList->sgEntry[*pCurEntryIndex].Length)
         {
             *pCurOffsetInEntry = 0
             *pCurEntryIndex ++
         }
     }

     return LeftTransferLen - length


逻辑很简单.按照MaxTransferLen划分切割就行了

接下来就是要真正处理active transfer的部分了
_USBPORT_CoreEndpointWorker(pEndpoint,bRecursion)
     if(!bRecursion)
     {
         if(pEndpoint->Busy)
            return 1
         else
            pEndpoint->Busy = 1
     }

     call _USBPORT_GetEndpointState(pEndpoint)
     state = return value
     if(state == closed)
     {
         pEndpoint->Busy = 0
         return 0
     }
     
     call _USBPORT_PollEndpoint(pEndpoint)
     call _USBPORT_GetEndpointState(pEndpoint)
     state = return value
     if(state == removed)
     {
         pEndpoint->CurrentState = close
         pEndpoint->NextState = close
         insert the endpoint to fdo\'s ClosedEndpointListHead
         pEndpoint->Busy = 0
         return 0
     }
     
     if(pEndpoint->ActiveTransfer is empty &&
        pEndpoint->PendingTransfer is empty &&
        pEndpoint->CancelTransfer is empty)
     {
        pEndpoint->Busy = 0
        call _USBPORT_FlushAbortList(pEndpoint)
        return 0
     }
     
     call _USBPORT_GetEndpointState(pEndpoint)
     state = return value
     if(state != pEndpoint->NextState)
     {
         pEndpoint->Busy = 0
         return 1
     }

     call pEndpoint->WorkRoutine(pEndpoint)

     call _USBPORT_FlushAbortList(pEndpoint)

     pEndpoint->Busy = 0

     return 0


眼见函数就要分析完了.这个家伙马上又引出来若干个函数..哈哈

其中_USBPORT_FlushAbortList函数在讲到cancel的时候再说
他其实是一个无关紧要的函数.

而_USBPORT_PollEndpoint函数则是
调用miniport对应的Poll函数
在这个函数里面,miniport driver总是检查自己的transfer有没有完成了的,如果有就告诉usbport自己完成了某个transfer

至于_USBPORT_GetEndpointState更是简单
如果pEndpoint->CurrentState == pEndpoint->NextState则返回CurrentState.否则返回一个特殊的值ENDPOINT_STATE_TRANSITION
这个值从来不会被设置到CurrentState跟NextState成员上面

至于这个state的用法这里先大致的说说
state表征了endpoint的当前状态
能够设置的值有active,pause,remove,close,后两种是在特别的情况下才设置的.当你关闭一个endpoint的时候(select configuration却填一个0的config desc),endpoint进入remove状态,上面已经看到他马上就转换成了close状态而进入了fdo的closed enpoint list,作lazy close.

而active状态就是正常状态.在这个状态下,endpoint把自己的active list里面的transfer一一提交给miniport driver
而pause的状态是为了处理cancel跟abort的情况的
如果active list里面的transfer有某个被标记成了canceled或者aborted.endpoint就从active状态进入pause状态.进入pause状态以后一一完成那些标记成canceled和aborted的transfer,直到全部都处理完了.就转入active状态.

那上面这些操作都是由谁来完成的呢..当然不用说也是pEndpoint->WorkRoutine要作的事情了

当然,状态的转换并不是这么简单的事情.一个最主要的原因是要同步endpoint的FrameNumber32Bit成员到当前usb host controller的Frame Number,这个主要是用于Iso传输的.所以状态转换的过程是个迂回的.
endpoint首先进入fdo的state change list,然后fdo让miniport driver在下一个start of frame的时候引发一个中断.在isr的dpc里面同步endpoint的Frame Number,然后把endpoint从state change list里面取出来,这样endpoint才算是进入了新的状态,也就是让endpoint的CurrentState跟NextState相同

然后要注意的是.这个函数如果返回值是1的话紧接着调用的就是
_USBPORT_InvalidateEndpoint函数

这个函数告诉一个辅助线程(通过设置一个Event),调用一次_USBPORT_CoreEndpointWorker

miniport driver也会在比如某个传输完成的时候调用这个函数来排队CoreEndpointWorker的调用.使得usbport能继续向其提交新的传输.而不是停下来

上面这段话是什么意思呢
看这个流程
进入pending -> flush pending -> 进入map ->flush map->MapTransfer进入 active->CoreWorker 提交一些但不是全部的transfer给硬件

这里就层层返回了..并没有连续的提交.
那要提交下一个怎么办呢.必须要再次进入CoreWorker才可以
这就是通过InvalidateEndpoint函数来完成的

Invalidate这个函数把你要Invalidate的Endpoint收集到一个list里面链接到fdo的AttendListHead上.然后唤醒work thread
work thread从fdo的AttendListHead上一一取出每个endpoint 然后调用CoreWorker.如果返回还是1,则保存到另外一个list上面
当第一遍循环完了.再把那些还是返回1的endpoint重新链接到fdo的AttendListHead上面.再次排队下一个CoreWorker调用,也就是再次设置Event,而不是直接循环再调用CoreWorker

希望能明白这种排队poll调用的机制
记住一点.transfer的处理是要通过调用CoreWorker才会传递给miniport然后传递给硬件的.InvalidateEndpoint正是引发一次CoreWorker调用的关键

然后来看看实际上的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 = 0
    }
    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 = 0
    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文件.似乎不应该是这样说说就能完完全全说明白的

实际上还有好多好多的东西都没有涉及到,比如电源管理,远程唤醒等等,毕竟是一个很大的驱动

[编辑 -  3/7/05 by  tiamo]
游客

返回顶部