Windows XP下usbport.sys驱动内部实现解析(一)
讲了USB驱动栈整体结构,说明了usbport.sys的重要作用,现在就具体分析usbport.sys的内部实现细节。
首先再重复一下:
- usbport 是一个USB主机控制器的port driver
- usbuhci是uhci类型的USB主机控制器的miniport driver
- usbehci则是ehci类型的USB主机控制器的miniport driver
usbport负责创建管理USB主机控制器的 FDO ( Function Device Object )。而 FDO 对应的 PDO (Physical Device Object) 是由谁创建的呢?当然是这个主机控制器所在总线的总线驱动创建管理的了 — 一般就是PCI总线驱动。
usbport主要完成的功能如下:
- 抽象层次上管理USB主机控制器,实际的操作是由各个miniport driver 完成的。
- 创建并管理集成在USB主机控制器里面的根总线的PDO,这个PDO上会附加由usbhub.sys创建并管理的FDO 对象。
- 管理维护所有挂在当前这个主机控制器上的最多127个usb 设备。
- 处理大多数针对当前这个主机控制器管理下的总线上的所有usb设备所发出的USB请求(URB)。
基本功能如上所述,这里就这几方面的功能一一描述。
1. 抽象层次上管理USB主机控制器
这个是集成到各个其他操作里面的,通用的操作由usbport完成,特定了和硬件相关的操作通过调用各个miniport所注册的callback routine来完成。USB主机控制器的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. 创建并管理集成在USB主机控制器里面的根总线的PDO
根总线的PDO创建很简单,不外乎就是在query bus relationship的是创建一个device并且返回就行了。至于关联到这个根总线PDO上的操作,大部分都交给了USB主机控制器 FDO 来完成了。马上就能看到其实大部分的操作都是发送给这个PDO的。
3.跟4.就是今天的重点部分了,这两个部分也是 usbport.sys里面着墨最多的地方。我讲的顺序也是先说第三部分,再说第四部分。但是这两个部分并不是完全分开的,比如设备管理的时候可能涉及到要向某个设备发送一些urb。最典型的就是要set address,这里当然就必须由第四个部分来完成了,而且urb的处理也极大的依赖管理设备的时候所建立和维护的诸多数据结构。但是为了讲解方便,在说到第三个部分的时候总是假设usbport有某种方法能把某个urb发送出去并且完成,暂时不管他具体是怎么完成的。
3. 管理维护所有挂在当前这个主机控制器上的最多127个usb 设备
在前面说到usbport实现了两个interface以提供usbhub.sys调用,并在usbhub的帮助下来收集所有连接到总线上的设备信息。这里我们跳过这两个interface的具体说明以及调用方式调用时间等等信息,等到讲到usbhub的时候再回头来看这个过程,那个时候再来说究竟在什么时候在什么样子的情况下usbhub会调用哪个函数。如果没有调用上下文要去分析一个函数的功能是比较盲目的,这里大家了解这样一个规则:usbhub用某种方式得知了总线上有个设备连接进来了(比如你插入了一个u盘)?usbhub是通过调用USBPORTBUSIF_CreateUsbDevice 通知usbport这个事件发生了(函数参数信息可以查阅ddk)。下面得分析也就是从这个地方开始的。
在经过必要的参数检查测试以后,usbport为这个新报告的设备分配一个结构,用来保存必要的信息。下面是结构体的成员:
struct DEVICE_HANDLE // (sizeof=0X70)
{
ULONG dwSignature; // signature = \'HveD\'
USHORT DeviceAddress; // usb device addr (1-127)
USHORT PortNumber; // port number
ULONG RefCount; // reference count
ULONG Tt; // for usb2.0
PVOID ParentHandle; // parent hub device handle
PCONFIG_HANDLE ConfigHandle; //
PIPE_HANDLE PipeHandle; // for default pipe
ULONG DeviceSpeed; // see ddk
USB_DEVICE_DESCRIPTOR DeviceDesc; //
UCHAR reserved; // padding,alignment
ULONG DeviceFlags; //
LIST_ENTRY DeviceHandleListEntry; // link all device handle together
LIST_ENTRY PipeHandleListHead; // pipe handle list header
ULONG TtCount; // for usb2.0
LIST_ENTRY TtListHead; // for usb2.0
};
usbport 设置好某些域以后,开始open the default pipe。完成以后,发送一个get device descriptor的urb到这个新出现的usb设备用来填写DeviceDesc成员。成功以后把这个新创建的device handle添加到由USB主机控制器所维护的device handle list上去。具体流程参看下面的伪代码:
createdevice(parenthandle,portnumber,portstatus)
{
检查参数是否合法
为DEVICE_HANDLE分配内存handle
handle->refcount = 0
handle->confighandle = 0
handle->parenthandle = parenthandle
handle->portnumber = portnumber
handle->deviceaddress = 0 // default usb address
根据portstatus,设置handle->devicespeed
根据devicespeed,设置默认 pipe 的maxpacketsize
初始化默认 pipe 的 pipe_handle 的其它成员变量
打开默认 pipe 的端点
得到设备的描述信息 device descriptor
将这个handle保存到 device handle list 链表中
}
在看open default pipe之前得先看看pipe handle这个结构:
struct PIPE_HANDLE // sizeof=0X20
{
ULONG dwSignature; // signature = \'HpiP\'
USB_ENDPOINT_DESCRIPTOR EndPointDesc;
UCHAR reserved; // padding,alignment
ULONG Flags // flags
ULONG unknown;
ENDPOINT* EndpointPointer;
LIST_ENTRY ListEntry;
};
很简单的一个结构。
现在来看open endpoint这个函数:1. 先照样作一些检查,然后为endpoint结构分配内存。因为这个endpoint结构不仅仅是usbport使用,miniport也会使用。所有这个结构的大小是不固定的,miniport在注册自己的时候会指定自己所需要的结构大小,usbport在这个大小的基础上加上自己所管理的大小来决定要分配多大的内存。2. 然后usbport作很多的初始化工作,设置大部分endpoint结构的成员,必要的时候还需要miniport的帮助。3. 完成以后把这个pipe handle连接到device的pipe handle list header上面,同时把新创建的endpoint也连接到由fdo维护的全局endpoint list header上面去。
endpoint是一个很大的结构,0x160个字节(checked xp版本),篇幅原因不一一列举了,看几个与我们分析相关的成员:
struct ENDPOINT // sizeof=0X160
{
ULONG dwSignature; // \'PEch\'
PDEVICE_OBJECT FunctionDevice; // host controller fdo
PDEVICE_HANDLE DeviceHandle; // device handle
ULONG CurrentState; // endpoint state
ULONG NextState; // hose two field will be protected by StateLock(see below)
ULONG FrameNumber32Bit; // for iso,current frame number
PWORK_ROUTINE WorkerRoutine; // core work routine
LIST_ENTRY ActiveTransfer; // activer transfer list header
LIST_ENTRY PendingTransfer; // pending transfer list header
LIST_ENTRY CancelTransferList; // cancel transfer list header
LIST_ENTRY AbortIrpList; // abort irp list header
LIST_ENTRY EndpointListEntry; // link all endpoints together to fdo\'s list header
LIST_ENTRY AttendLink; // see below
LIST_ENTRY StateChangeListEntry; // endpoint state change list entry linked to fdo\'s list header
LIST_ENTRY ClosedLink; // for lazy close,linked to fdo\'s list header
LIST_ENTRY FlushList; // flush list entry,linked to fdo
USB_LOCK ResourceLock; // spin lock for this struct
USB_LOCK StateLock; // spin lock for endpoint\'s state
KIRQL SavedResIrql; // save irpl
KIRQL SavedStateIrql;
COMMON_BUFFER* CommonBuffer; // for miniport driver COMMON_BUFFER*
USHORT DeviceAddress; // device addr,copied from device handle
USHORT EndPointAddr; // endpoint number
ULONG MaxPacketSize; // max packet size
UCHAR Period; // period
ULONG DeviceSpeed;
ULONG BandWidth;
ULONG Offset; // schedule offset
ULONG TransferType; //
ULONG Direction; // in or out
ULONG CommonBufferVir; // for common buffer
ULONG CommonBufferPhy; // for common buffer
ULONG CommonBufferLen; // for common buffer
ULONG MaxTransferLen;
USHORT PortNumber;
PVOID ClientContextPointer; // point to miniport endpoint context
};
许多成员的作用就像他的名字所描述的一样,不过有几个特别提出要特别注意的地方。
首先是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的结构:
struct CONFIG_HANDLE //sizeof=0X14
{
ULONG dwSignature; // \'HgfC\'
PVOID ConfigDescPointer; // ptr config desc buffer
LIST_ENTRY InterfaceListHead; // link interfaces together
USB_CONFIGURATION_DESCRIPTOR ConfigDesc;
};
很简单的一个变长结构(因为configurtation descriptor是变长的),实际上的configuration descriptor就跟在这个结构的末尾,从0ffset 0x10开始。当然,device handle结构的config handle成员指向了这个新分配的内存。
接下来发送一个set configuration的urb到设备。然后一一open 这个select configuration里面所描述的interface,并且填写将来要返回的USBD_INTERFACE_INFORMATION结构。填写 information结构是一个很简单的操作,所有必须的信息都在前面的步骤里面完成了,我们只是看看open interface完成的操作:
首先还是要分配结构:
struct INTERFACE_HANDLE
{
ULONG dwSignature; // \'HxfI\'
LIST_ENTRY ListEntry; // linked to config handle\'s interface list header
ULONG HasAltSetting;
USB_INTERFACE_DESCRIPTOR InterfaceDesc;
UCHAR reserved[3]; // Padding db 3 dup(?)
PIPE_HANDLE PipeHandle;
};
这个结构是变长的,结尾由若干个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的处理。
原文转自:https://blog.csdn.net/killcpp/article/details/7287096