打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Windows XP下usbhub.sys驱动内部实现解析(一)
Windows XP下usbhub.sys驱动内部实现解析(一)
2010-01-25 13:12
说过了usbport.sys驱动,现在来说说usbhub.sys驱动。

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:

struct SERIAL_NUMBER_TABLE_ENTRY  // sizeof=0XC
{
ULONG idVendor;
ULONG idProduct;
ULONG PhysicalDevice;
}

然后把这些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的处理情况。

5.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进行排队、传输、完成。

5.2 reset port 220007h

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

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
}

5.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处理呢?

5.4 get port status 220013h

usbhub处理,发送control urb到hub硬件获得这个port status,然后返回。

5.5 enable port 220017h

usbhub处理,同上。

5.6 get hub count 22001bh

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

5.7 get hub name 220020h

usbhub处理,返回hub的symlink name。

5.8 get bus info 220420h

通过interface的方式交由usbport处理完成。

5.9 get controll name 220424h

同上

5.10 get parent hub info 22042ch

usbhub pdo返回自己的parent fdo对应的pdo,以及自己的port number。

5.11 get device handle 220423h

usbhub pdo返回保存在自己device extension里面的device handle。

5.12 cycle port 22001Fh

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

5.13 notification 220027h

这个稍微有些复杂,因为他是和power management关联在一起的,说完power management再回头来看它的处理过程。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
DDK开发介绍-1
驱动开发学习笔记
用 VB 做 USB 通信程序及USB基础知识
【心经】 浅谈 windows 驱动开发
IRP_MN_START_DEVICE分发例程中的前进和等待IRP总结
USB转串口驱动代码分析
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服