Windows XP下usbport.sys驱动内部实现解析(一)
讲了USB驱动栈整体结构,说明了usbport.sys的重要作用,现在就具体分析usbport.sys的内部实现细节。
首先再重复一下:
- usbd 是USB1.1的类驱动程序,支持的是USB1.1
- usbport 是一个USB主机控制器的port driver,支持的USB2.0
- 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是USB主机控制器的类驱动程序,针对不同的EHCI硬件厂商,提供统一的抽象回调函数实现EHCI主机控制器的全部功能和流程。
USBechic是USBPort不对硬件函数功能的具体硬件。其通过USBPort导出的函数USBPORT_RegisterUSBPortDriver向USBPort驱动注册一个大在的结构体USBPORT_REGISTRATION_PACKET。USBPORT_REGISTRATION_PACKET中包含了USBPort需要的各种针对硬件的相关接口实现。
USBPORT_REGISTRATION_PACKET在XP中分为2个版本:
#define USB_MINIPORT_HCI_VERSION_1 100
#define USB_MINIPORT_HCI_VERSION_2 200
typedef struct _USBPORT_REGISTRATION_PACKET_V1 {
...
} USBPORT_REGISTRATION_PACKET_V1, *PUSBPORT_REGISTRATION_PACKET_V1;
/*
Miniport version 2 (current) api packet
*/
typedef struct _USBPORT_REGISTRATION_PACKET {
...
} USBPORT_REGISTRATION_PACKET, *PUSBPORT_REGISTRATION_PACKET;
根据版本不同,处理不同。
NTSTATUS
USBPORT_RegisterUSBPortDriver(
PDRIVER_OBJECT DriverObject,
ULONG MiniportHciVersion, //100 or 200
PUSBPORT_REGISTRATION_PACKET RegistrationPacket
)
在USBPORT_RegisterUSBPortDriver中不仅会重新备份ECHIC传过来的函数集,也会设置各个IRP的回调函数和AddDevice回调如USBPORT_PnPAddDevice。这样当一个USB主机控制器被系统识别后,会分别调用其对应的AddDevice创建对应的FDO,并附加到PCI创建的PDO上。
AddDevice中创建的设备名格式为\\Device\\USBFDO-x
在IRP_MJ_PNP的StartDevice的USBPORT_CreateLegacyFdoSymbolicLink中创建其链接名格式为\\DosDevices\\HCDx
2. 创建并管理集成在USB主机控制器里面的根总线的PDO
在IRP_MN_QUERY_DEVICE_RELATIONS请求中,当请求关系类型为BusRelations时,用于获取子设备。USB主机控制器通过其RootHubPdo指针指向根设备的PDO。
if (devExt->Fdo.RootHubPdo == NULL) {
// create a new root hub
ntStatus = USBPORT_CreateRootHubPdo(FdoDeviceObject,&devExt->Fdo.RootHubPdo);
}
USBPORT_CreateRootHubPdo创建的子设备的名称格式为\\Device\\USBPDO-X
USB主机控制器对于IRP_MJ_INTERNAL_DEVICE_CONTROL请求没有支持。
USB主机控制器对于IRP_MJ_DEVICE_CONTROL请求支持以下IOCTL
USBDIAG获取信息:
| IOCTL | 功能 | 实现机制 |
|---|---|---|
| IOCTL_USB_DIAGNOSTIC_MODE_ON | 启用诊断模式 | 设置设备扩展标志 USBPORT_FDOFLAG_DIAG_MODE |
| IOCTL_USB_DIAGNOSTIC_MODE_OFF | 禁用诊断模式 | 清除设备扩展标志 USBPORT_FDOFLAG_DIAG_MODE |
| IOCTL_GET_HCD_DRIVERKEY_NAME | 获取 HCD 驱动注册表键名 | 调用 USBPORT_LegacyGetUnicodeName,内部调用 USBPORT_UserGetControllerKey |
| IOCTL_USB_GET_ROOT_HUB_NAME | 获取根集线器符号链接名 | 调用 USBPORT_LegacyGetUnicodeName,内部调用 USBPORT_UserGetRootHubName |
Roothub Symbolic Link : USB#ROOT_HUB30#4&2dd4af6a&2&0#{f18a0e88-c30c-11d0-8815-00a0c906bed8}
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的处理。
本文链接为:http://www.usbzh.com/article/detail-1392.html ,欢迎转载,转载请附上本文链接。原文转自:https://blog.csdn.net/killcpp/article/details/7287096
Windows XP下usbport





