Windows驱动_USB驱动之四

时间:2021-07-19 17:42:43

                 论程序员的正能量,目前我们整个社会,被贪腐,拼爹,关系等这些乌烟瘴气笼罩着,我们存在于这个社会上,无法摆脱这所有的种种,但我们凭个人的能力无法改变它的时候,我们应该改变自己,改变当下,在程序员的这个圈子里,很多程序员都觉得自己的公司不好,怎么怎么样剥削自己等等。我在这里想说的是,这个是你心甘情愿的,没有人强迫你,你完全可以再次选择,我们竟然选择了这个公司,就应该好好为它工作,为它的将来打拼,相信大多数的公司都是这样的,公司发展好了,个人必将受益,公司不会亏待你,相反如果你不好好待你的公司,相信公司也不会好好的对待你。我们程序员,需要这个正能量。

 

                我们今天继续来看一下,MSDN上面关于USB的例子代码,我们知道所有的USB设备都是菊链形势连接在总线上的,菊链指的是连接方式,HUB上面可以接HUB,也可以接设备,比如有10层HUB,在第10层HUB上面连接的设备跟在第2层HUB上面连接的设备,如果标准一直,比如都是2.0的设备,理论上面传输速度是一样的,为什么了,因为,USB都是通过管道进行传输的,而管道跟管道之间是独立的,不会相互影响的。所以理论的速度一致。我们在USB设备驱动中也是通过这个管道进行传送,我们要向跟我们连接的总线去声明所用的管道,是输入还是输出,是什么样类型的管道等等,有很多管道信息。所以我们在真正涉及到USB总线和设备进行数据交换的时候,会用到大量的管道信息,事实上我们的一个管道就是一个IOTARGET,这个IOTARGET就是跟USB总线沟通的桥梁,我们可以根据它来和USB总线驱动进行通讯。

 

               我们在上面的USB驱动第三节,后面还有几个函数没有讲完,今天,我们继续看一下,device.cpp的491行,ReadAndSelectDescriptors在调用的status = ConfigureDevice(Device);我们来看一下这个函数:

 

NTSTATUS
ConfigureDevice(
_In_ WDFDEVICE Device
)
/*++

Routine Description:

This helper routine reads the configuration descriptor
for the device in couple of steps.

Arguments:

Device - Handle to a framework device

Return Value:

NTSTATUS - NT status value

--*/
{
USHORT size = 0;
NTSTATUS status;
PDEVICE_CONTEXT pDeviceContext;
PUSB_CONFIGURATION_DESCRIPTOR configurationDescriptor;
WDF_OBJECT_ATTRIBUTES attributes;
WDFMEMORY memory;

PAGED_CODE();

//
// initialize the variables
//
configurationDescriptor = NULL;
pDeviceContext = GetDeviceContext(Device);

//
// Read the first configuration descriptor
// This requires two steps:
// 1. Ask the WDFUSBDEVICE how big it is
// 2. Allocate it and get it from the WDFUSBDEVICE
//
status = WdfUsbTargetDeviceRetrieveConfigDescriptor(pDeviceContext->WdfUsbTargetDevice,
NULL,
&size);

if (status != STATUS_BUFFER_TOO_SMALL || size == 0) {
return status;
}

//
// Create a memory object and specify usbdevice as the parent so that
// it will be freed automatically.
//
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);

attributes.ParentObject = pDeviceContext->WdfUsbTargetDevice;

status = WdfMemoryCreate(&attributes,
NonPagedPool,
POOL_TAG,
size,
&memory,
&configurationDescriptor);
if (!NT_SUCCESS(status)) {
return status;
}

status = WdfUsbTargetDeviceRetrieveConfigDescriptor(pDeviceContext->WdfUsbTargetDevice,
configurationDescriptor,
&size);
if (!NT_SUCCESS(status)) {
return status;
}

pDeviceContext->UsbConfigurationDescriptor = configurationDescriptor;

status = SelectInterfaces(Device);

return status;
}


这里得到了控制描述符,通过WdfUsbTargetDeviceRetrieveConfigDescriptor,因为不清楚有几个描述符,所以调用了这个函数两次,因为不清楚设备有几个控制描述符,也就是设备有几种配置,所以第一次调用为了得到整个控制描述符的大小,第二次才是得到真正的控制描述符。下面调用了status = SelectInterfaces(Device);我们猜测应该是选择控制描述符,也就是选择设备配置,一般MSFT使用第一种配置。我们看一下这个函数:

NTSTATUS
SelectInterfaces(
_In_ WDFDEVICE Device
)
/*++

Routine Description:

This helper routine selects the configuration, interface and
creates a context for every pipe (end point) in that interface.

Arguments:

Device - Handle to a framework device

Return Value:

NT status value

--*/
{
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;
NTSTATUS status;
PDEVICE_CONTEXT pDeviceContext;
UCHAR i;
WDF_OBJECT_ATTRIBUTES pipeAttributes;
WDF_USB_INTERFACE_SELECT_SETTING_PARAMS selectSettingParams;
UCHAR numberAlternateSettings = 0;
UCHAR numberConfiguredPipes;

PAGED_CODE();

pDeviceContext = GetDeviceContext(Device);

//
// The device has only one interface and the interface may have multiple
// alternate settings. It will try to use alternate setting zero if it has
// non-zero endpoints, otherwise it will try to search an alternate
// setting with non-zero endpoints.
//

WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE( &configParams);

WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pipeAttributes, PIPE_CONTEXT);

#if (NTDDI_VERSION >= NTDDI_WIN8)
pipeAttributes.EvtCleanupCallback = UsbSamp_EvtPipeContextCleanup;
#endif

status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->WdfUsbTargetDevice,
&pipeAttributes,
&configParams);


if (NT_SUCCESS(status) &&
WdfUsbTargetDeviceGetNumInterfaces(pDeviceContext->WdfUsbTargetDevice) > 0) {

status = RetrieveDeviceInformation(Device);
if (!NT_SUCCESS(status)) {
UsbSamp_DbgPrint(1, ("RetrieveDeviceInformation failed %x\n", status));
return status;
}

pDeviceContext->UsbInterface =
configParams.Types.SingleInterface.ConfiguredUsbInterface;

//
// This is written to work with Intel 82930 board, OSRUSBFX2, FX2 MUTT and FX3 MUTT
// devices. The alternate setting zero of MUTT devices don't have any endpoints. So
// in the code below, we will walk through the list of alternate settings until we
// find one that has non-zero endpoints.
//

numberAlternateSettings = WdfUsbInterfaceGetNumSettings(pDeviceContext->UsbInterface);

NT_ASSERT(numberAlternateSettings > 0);

numberConfiguredPipes = 0;

for (i = 0; i < numberAlternateSettings && numberConfiguredPipes == 0; i++) {

WDF_USB_INTERFACE_SELECT_SETTING_PARAMS_INIT_SETTING(&selectSettingParams, i);

WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pipeAttributes, PIPE_CONTEXT);

#if (NTDDI_VERSION >= NTDDI_WIN8)
pipeAttributes.EvtCleanupCallback = UsbSamp_EvtPipeContextCleanup;
#endif

status = WdfUsbInterfaceSelectSetting(pDeviceContext->UsbInterface,
&pipeAttributes,
&selectSettingParams
);

if (NT_SUCCESS(status)) {

numberConfiguredPipes = WdfUsbInterfaceGetNumConfiguredPipes(pDeviceContext->UsbInterface);

if (numberConfiguredPipes > 0){

pDeviceContext->SelectedAlternateSetting = i;

}

}

}

pDeviceContext->NumberConfiguredPipes = numberConfiguredPipes;

for (i = 0; i < pDeviceContext->NumberConfiguredPipes; i++) {
WDFUSBPIPE pipe;

pipe = WdfUsbInterfaceGetConfiguredPipe(pDeviceContext->UsbInterface,
i, //PipeIndex,
NULL
);
#if (NTDDI_VERSION >= NTDDI_WIN8)
if (pDeviceContext->IsDeviceSuperSpeed) {
status = InitializePipeContextForSuperSpeedDevice(pDeviceContext,
pipe);
}
else if (pDeviceContext->IsDeviceHighSpeed) {
status = InitializePipeContextForHighSpeedDevice(pipe);
}
else {
status = InitializePipeContextForFullSpeedDevice(pipe);
}
#else
if (pDeviceContext->IsDeviceHighSpeed) {
status = InitializePipeContextForHighSpeedDevice(pipe);
}
else {
status = InitializePipeContextForFullSpeedDevice(pipe);
}
#endif
if (!NT_SUCCESS(status)) {
UsbSamp_DbgPrint(1, ("InitializePipeContext failed %x\n", status));
break;
}
}

}

return status;
}


这个函数很长,因为不清楚,设备使用的环境,比如操作系统的版本,因为WIN8,有对USB3.0做一些特殊的处理,还有设备是1.0,还是2.0,或3.0,这里有分开做不同的处理,我们慢慢看一下:

我们最终需要选择设备,也就是调用这个函数:

NTSTATUS     WdfUsbTargetDeviceSelectConfig(
    IN WDFUSBDEVICE  
UsbDevice
,
    IN OPTIONAL PWDF_OBJECT_ATTRIBUTES  
PipeAttributes
,
    IN OUT PWDF_USB_DEVICE_SELECT_CONFIG_PARAMS  
Params
    );

这个函数的前两个参数很简单,第一个我们已经调用WdfUsbTargetDeviceCreateWithParameters这个得到了,第二个是标准的WDF对象属性,随便使用WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE宏初始化一下,注意这里使用了一个PIPE_CONTEXT

typedef struct _PIPE_CONTEXT {

    ULONG NextFrameNumber;   
   
    ULONG   TransferSizePerMicroframe;

    ULONG   TransferSizePerFrame;

    BOOLEAN  StreamConfigured;

#if (NTDDI_VERSION >= NTDDI_WIN8)
    USBSAMP_STREAM_INFO    StreamInfo;
#endif

} PIPE_CONTEXT, *PPIPE_CONTEXT;

因为这里我们知道,这个设备只有一个接口,摄像头,没有其他的功能,所以可以使用WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE这个宏来初始化第三个参数。

成功返回后,我们调用WdfUsbTargetDeviceGetNumInterfaces得到接口的数目,这里肯定大于0的,然后调用RetrieveDeviceInformation得到设备其他的信息,这里,我需要强调一点的是,后面的这个代码,       

 pDeviceContext->UsbInterface =
            configParams.Types.SingleInterface.ConfiguredUsbInterface;

这里的SingleInterface对应于我们配置的类型WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE,同理我们根据如下的这个结构体,知道我们还可以配置另外的设备用不同的类型,例如:

WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_DECONFIG

WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE

WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES

WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_INTERFACES_DESCRIPTORS

WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_URB

 

typedef struct _WDF_USB_DEVICE_SELECT_CONFIG_PARAMS {
  ULONG  
Size;
  WdfUsbTargetDeviceSelectConfigType  
Type;
  union {
    struct {
      PUSB_CONFIGURATION_DESCRIPTOR  ConfigurationDescriptor;
      PUSB_INTERFACE_DESCRIPTOR*  InterfaceDescriptors;
      ULONG NumInterfaceDescriptors;
    } Descriptor;
    struct {
      PURB  Urb;
    } Urb;
    struct {
      UCHAR  NumberConfiguredPipes;
      WDFUSBINTERFACE  ConfiguredUsbInterface;
    } SingleInterface;
    struct {
      UCHAR  NumberInterfaces;
      PWDF_USB_INTERFACE_SETTING_PAIR  Pairs;
      UCHAR  NumberOfConfiguredInterfaces;
    } MultiInterface;
  } Types;
} WDF_USB_DEVICE_SELECT_CONFIG_PARAMS, *PWDF_USB_DEVICE_SELECT_CONFIG_PARAMS;

 

我们继续往下看,配置已经选了,因为一个接口,还有很多种的设置,我们还要选择哪一种设置。我们首先通过WdfUsbInterfaceGetNumSettings这个函数得到接口的设置有多少种,然后来配置每一种接口的设置,这里,我们还是先看下这个函数:

NTSTATUS  WdfUsbInterfaceSelectSetting(
    IN WDFUSBINTERFACE  
UsbInterface
,
    IN OPTIONAL PWDF_OBJECT_ATTRIBUTES  
PipesAttributes
,
    IN PWDF_USB_INTERFACE_SELECT_SETTING_PARAMS  
Params
    );

上面是设备的配置所以使用的WDFUSBDEVICE,这里是接口的设置,所以成为WDFUSBINTERFACE,我上面强调了的,这个刚刚已经设置了,还是第三个参数,有点麻烦,这里用到了VOID    WDF_USB_INTERFACE_SELECT_SETTING_PARAMS_INIT_SETTING(
    OUT PWDF_USB_INTERFACE_SELECT_SETTING_PARAMS  
Params
,
    IN UCHAR  
SettingIndex
    );
连起来,我们就知道了,知道了这个接口有多少中设置,然后通过这个最大值,从0开始一个一个开始配置就好,如果配置成功一个,我们来看总线给的管道也就是PIPE的数,如果PIPE数大于0,接口的设置选择就结束,看下循环的条件: for (i = 0; i < numberAlternateSettings && numberConfiguredPipes == 0; i++)

下面,我们来配置PIPE,回忆一下啊,首先是设备配置,然后是接口配置,然后是接口的设定配置,最后是接口的设置配置完后的管道配置,通过WdfUsbInterfaceGetNumConfiguredPipes这个函数得到配置成功的接口有几个管道,下面肯定是一个一个的进行配置。

我们先不往下看,因为这里用到另外一个函数RetrieveDeviceInformation得到一些设备的其他配置,我们看下这个函数:

NTSTATUS
RetrieveDeviceInformation(
_In_ WDFDEVICE Device
)
{
PDEVICE_CONTEXT pDeviceContext;
WDF_USB_DEVICE_INFORMATION info;
NTSTATUS status;
USHORT numberOfStreams = 0;
PAGED_CODE();

pDeviceContext = GetDeviceContext(Device);

WDF_USB_DEVICE_INFORMATION_INIT(&info);

//
// Retrieve USBD version information, port driver capabilites and device
// capabilites such as speed, power, etc.
//
status = WdfUsbTargetDeviceRetrieveInformation(pDeviceContext->WdfUsbTargetDevice,
&info);
if (!NT_SUCCESS(status)) {
return status;
}

pDeviceContext->IsDeviceHighSpeed =
(info.Traits & WDF_USB_DEVICE_TRAIT_AT_HIGH_SPEED) ? TRUE : FALSE;

UsbSamp_DbgPrint(3, ("DeviceIsHighSpeed: %s\n",
pDeviceContext->IsDeviceHighSpeed ? "TRUE" : "FALSE"));

UsbSamp_DbgPrint(3, ("IsDeviceSelfPowered: %s\n",
(info.Traits & WDF_USB_DEVICE_TRAIT_SELF_POWERED) ? "TRUE" : "FALSE"));

pDeviceContext->WaitWakeEnable =
info.Traits & WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE;

UsbSamp_DbgPrint(3, ("IsDeviceRemoteWakeable: %s\n",
(info.Traits & WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE) ? "TRUE" : "FALSE"));

status = GetStackCapability(Device,
&GUID_USB_CAPABILITY_DEVICE_CONNECTION_SUPER_SPEED_COMPATIBLE,
0,
NULL);
if (NT_SUCCESS(status)) {
pDeviceContext->IsDeviceSuperSpeed = TRUE;
}

UsbSamp_DbgPrint(3, ("DeviceIsSuperSpeed: %s\n",
pDeviceContext->IsDeviceSuperSpeed ? "TRUE" : "FALSE"));

if (pDeviceContext->IsDeviceSuperSpeed == TRUE) {
status = GetStackCapability(Device,
&GUID_USB_CAPABILITY_STATIC_STREAMS,
sizeof(numberOfStreams),
(PUCHAR)&numberOfStreams);
if (NT_SUCCESS(status)) {
pDeviceContext->IsStaticStreamsSupported = TRUE;
pDeviceContext->NumberOfStreamsSupportedByController = numberOfStreams;
}

if (pDeviceContext->IsStaticStreamsSupported) {
UsbSamp_DbgPrint(3, ("Number of Streams supported by the controller: %d\n", numberOfStreams));
}
}

return STATUS_SUCCESS;
}


这个函数非常简单,使用两个核心函数WdfUsbTargetDeviceRetrieveInformation和GetStackCapability,其他的代码都是把得到的信息保存在设备上下文中。我们先看

NTSTATUS  WdfUsbTargetDeviceRetrieveInformation(
    IN WDFUSBDEVICE  
UsbDevice
,
    OUT PWDF_USB_DEVICE_INFORMATION  
Information
    );
这个函数含简单,只要使用WDFUSBDEVICE  就可以得到设备其他的一些信息

typedef struct _WDF_USB_DEVICE_INFORMATION {
  ULONG  
Size;
  USBD_VERSION_INFORMATION  
UsbdVersionInformation;
  ULONG  
HcdPortCapabilities;
  ULONG  
Traits;
} WDF_USB_DEVICE_INFORMATION, *PWDF_USB_DEVICE_INFORMATION;

typedef struct _USBD_VERSION_INFORMATION {
  ULONG  
USBDI_Version;
  ULONG  
Supported_USB_Version;
} USBD_VERSION_INFORMATION, *PUSBD_VERSION_INFORMATION;

typedef enum _WDF_USB_DEVICE_TRAITS {
  
WDF_USB_DEVICE_TRAIT_SELF_POWERED = 0x00000001,
  
WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE = 0x00000002,
  
WDF_USB_DEVICE_TRAIT_AT_HIGH_SPEED = 0x00000004,
} WDF_USB_DEVICE_TRAITS;

关于GetStackCapability这个函数,不详解,这个是在WIN8新加的关于USB3.0的一些设置吧,后面有机会再看。

我们回到,这个关于PIPE的配置,上面说到用一个PIPE的索引号来进行配置,因为我们可以得到是否是IsDeviceHighSpeed从设备的其他信息中,还有从GetStackCapability这个函数得到设备是否是IsDeviceSuperSpeed,剩下的就是IsDeviceFullSpeed,我们选取一个分支继续往下,其他的分支差不多的,我们选择InitializePipeContextForHighSpeedDevice这个函数:

 

NTSTATUS
InitializePipeContextForHighSpeedDevice(
_In_ WDFUSBPIPE Pipe
)
/*++

Routine Description

This function validates all the isoch related fields in the endpoint descriptor
to make sure it's in comformance with the spec and Microsoft core stack
implementation and intializes the pipe context.

The TransferSizePerMicroframe and TransferSizePerFrame values will be
used in the I/O path to do read and write transfers.

Return Value:

NT status value

--*/
{
WDF_USB_PIPE_INFORMATION pipeInfo;
PPIPE_CONTEXT pipeContext;

PAGED_CODE();

WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
WdfUsbTargetPipeGetInformation(Pipe, &pipeInfo);

//
// We use the pipe context only for isoch endpoints.
//
if ((WdfUsbPipeTypeIsochronous != pipeInfo.PipeType)) {
return STATUS_SUCCESS;
}

pipeContext = GetPipeContext(Pipe);

if (pipeInfo.MaximumPacketSize == 0) {
UsbSamp_DbgPrint(1, ("MaximumPacketSize in the pipeInfo is invalid (zero)\n"));
return STATUS_INVALID_PARAMETER;
}

//
// Universal Serial Bus Specification Revision 2.0 5.6.3 Isochronous Transfer
// Packet Size Constraints: High-speed endpoints are allowed up to 1024-byte data
// payloads per microframe and allowed up to a maximum of 3 transactions per microframe.
//
// For highspeed isoch endpoints, bits 12-11 of wMaxPacketSize in the endpoint descriptor
// specify the number of additional transactions oppurtunities per microframe.
// 00 - None (1 transaction per microframe)
// 01 - 1 additional (2 per microframe)
// 10 - 2 additional (3 per microframe)
// 11 - Reserved.
//
// Note: MaximumPacketSize of WDF_USB_PIPE_INFORMATION is already adjusted to include
// additional transactions if it is a high bandwidth pipe.
//

if (pipeInfo.MaximumPacketSize > 1024 * 3) {
UsbSamp_DbgPrint(1, ("MaximumPacketSize in the endpoint descriptor is invalid (>1024*3)\n"));
return STATUS_INVALID_PARAMETER;
}

//
// Microsoft USB stack only supports bInterval value of 1, 2, 3 and 4 (or polling period of 1, 2, 4 and 8).
//
if (pipeInfo.Interval == 0 || pipeInfo.Interval > 4) {
UsbSamp_DbgPrint(1, ("bInterval value in pipeInfo is invalid (0 or > 4)\n"));
return STATUS_INVALID_PARAMETER;
}

pipeContext->TransferSizePerMicroframe = pipeInfo.MaximumPacketSize;

//
// For high-speed isochronous endpoints, the bInterval value is used
// as the exponent for a 2^(bInterval-1) value expressed in
// microframes; e.g., a bInterval of 4 means a period of 8 (2^(4-1))
// microframes. The bInterval value must be from 1 to 16. NOTE: The
// USBPORT.SYS driver only supports high-speed isochronous bInterval
// values of {1, 2, 3, 4}.
//
switch (pipeInfo.Interval) {
case 1:
//
// Transfer period is every microframe (8 times a frame).
//
pipeContext->TransferSizePerFrame = pipeContext->TransferSizePerMicroframe * 8;
break;

case 2:
//
// Transfer period is every 2 microframes (4 times a frame).
//
pipeContext->TransferSizePerFrame = pipeContext->TransferSizePerMicroframe * 4;
break;

case 3:
//
// Transfer period is every 4 microframes (2 times a frame).
//
pipeContext->TransferSizePerFrame = pipeContext->TransferSizePerMicroframe * 2;
break;

case 4:
//
// Transfer period is every 8 microframes (1 times a frame).
//
pipeContext->TransferSizePerFrame = pipeContext->TransferSizePerMicroframe;
break;
}

UsbSamp_DbgPrint(1, ("MaxPacketSize = %d, bInterval = %d\n",
pipeInfo.MaximumPacketSize,
pipeInfo.Interval));

UsbSamp_DbgPrint(1, ("TransferSizePerFrame = %d, TransferSizePerMicroframe = %d\n",
pipeContext->TransferSizePerFrame,
pipeContext->TransferSizePerMicroframe));

return STATUS_SUCCESS;
}


这个函数虽然长,但是逻辑很简单,首先从我们的选择的要进行的PIPE结构中得到PIPE信息,调用

WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
WdfUsbTargetPipeGetInformation(Pipe, &pipeInfo);

 

typedef struct _WDF_USB_PIPE_INFORMATION{
  ULONG  
Size;
  ULONG  
MaximumPacketSize;
  UCHAR  
EndpointAddress;
  UCHAR  
Interval;
  UCHAR  
SettingIndex;
  WDF_USB_PIPE_TYPE  
PipeType;
  ULONG  
MaximumTransferSize;
} WDF_USB_PIPE_INFORMATION, *PWDF_USB_PIPE_INFORMATION;

 

typedef enum _WDF_USB_PIPE_TYPE {
  
WdfUsbPipeTypeInvalid = 0,
  
WdfUsbPipeTypeControl,
  
WdfUsbPipeTypeIsochronous,
  
WdfUsbPipeTypeBulk,
  
WdfUsbPipeTypeInterrupt
} WDF_USB_PIPE_TYPE;

后面的代码,就是进行一些PIPE的配置,和一些PIPE参数的检测,比如MaximumPacketSize和TransferSizePerMicroframe这些都在USB的DataSheet里面有说明,后续进行讲解。