如何将数据传输到 USB 常时等量终结点

本主题介绍客户端驱动程序如何生成 USB 请求块 (URB) 以在 USB 设备中向/从常时等量终结点传输数据。

通用串行总线 (USB) 设备可以支持常时等量终结点,以稳定速率传输依赖于时间的数据,例如音频/视频流。 若要传输数据,客户端驱动程序会发出请求,以将数据读取或写入常量终结点。 因此,主机控制器启动常时等量传输,该传输通过定期轮询设备来发送或接收数据。

对于高速和全速设备,轮询通过使用 (IN/OUT) 令牌数据包来完成。 当终结点准备好发送数据时,设备将通过发送数据来响应其中一个 IN 令牌数据包。 若要写入设备,主机控制器会发送 OUT 令牌数据包,后跟数据包。 主机控制器或设备不发送任何握手数据包,因此无法保证传递。 由于主机控制器不会尝试重试传输,因此如果发生错误,数据可能会丢失。

对于常时等量传输,主机控制器在总线上保留特定时间段。 为了管理常时等量终结点的保留时间,将时间划分为连续的逻辑间隔,称为 总线间隔。 总线间隔的单位取决于总线速度。

对于全速,总线间隔是 。 帧的长度为 1 毫秒。

对于高速和超高速,总线间隔是微帧。 微帧的长度为 125 微秒。 八个连续的微帧构成一个高速或超高速帧。

常时等量传输基于数据包。 本主题中的术语 常时等量数据包 是指在一个总线间隔内传输的数据量。 终结点的特征决定了每个数据包的大小是固定的,由终结点的特征决定。

客户端驱动程序通过为请求创建 URB 并将 URB 提交到 USB 驱动程序堆栈来启动常时等量传输。 请求由 USB 驱动程序堆栈中的某个较低驱动程序处理。 收到 URB 后,USB 驱动程序堆栈将执行一组验证并计划请求的事务。 对于全速,要在每个总线间隔内传输的常时等量数据包包含在线路上的单个事务中。 某些高速设备允许在一个总线间隔内进行多个事务。 在这种情况下,客户端驱动程序可以在单个请求中发送或接收常量数据包中的更多数据, (URB) 。 SuperSpeed 设备支持多个事务和突发传输,允许每个总线间隔更多的字节数。 有关突发传输的详细信息,请参阅 USB 3.0 规范页 9-42。

准备工作

在创建常时等量传输请求之前,必须具有有关为常时等量终结点打开的管道的信息。

使用 Windows 驱动程序模型 (WDM) 例程的客户端驱动程序在USBD_INTERFACE_LIST_ENTRY数组的某个USBD_PIPE_INFORMATION结构中具有管道信息。 客户端驱动程序在驱动程序的上一个请求中获取了该数组,以选择设备中的配置或接口。

Windows 驱动程序框架 (WDF) 客户端驱动程序必须获取对框架的目标管道对象的引用,并调用 WdfUsbTargetPipeGetInformation 以获取 WDF_USB_PIPE_INFORMATION 结构中的管道信息。

根据管道信息确定这组信息:

  • 主机控制器可以向每个数据包中的管道发送多少数据。

    客户端驱动程序可以在请求中发送的数据量不能超过主机控制器可从终结点发送或接收的最大字节数。 最大字节数由 USBD_PIPE_INFORMATIONWDF_USB_PIPE_INFORMATION 结构的 MaximumPacketSize 成员指示。 USB 驱动程序堆栈在选择配置或选择接口请求期间设置 MaximumPacketSize 值。

    对于全速设备, MaximumPacketSize 派生自终结点描述符的 wMaxPacketSize 字段的前 11 位,该字段指示终结点可以在事务中发送或接收的最大字节数。 对于全速设备,控制器为每个总线间隔发送一个事务。

    在高速常时等量传输中,如果终结点允许,主机控制器可以在总线间隔内发送其他事务。 其他事务数由设备设置,并在 wMaxPacketSize 的位 12..11 中指示。 该数字可以是 0、1 或 2。 如果 12..11 指示 0,则终结点不支持每个微帧的其他事务。 如果数字为 1,则主机控制器可以发送额外的事务 (每个微帧) 总共两个事务;2 表示每个微帧) 两个事务 (总共三个事务。 由 USB 驱动程序堆栈设置的 MaximumPacketSize 值包括可在其他事务中发送的字节数。

    对于 SuperSpeed 常时等时传输,USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR (的某些值请参阅 Usbspec.h) 非常重要。 USB 驱动程序堆栈使用这些值来计算总线间隔内的最大字节数。

    • 终结点配套描述符的等时常量.Mult 字段。 在 SuperSpeed 等时常量传输中, (与高速设备) 的附加事务称为突发事务。 Mult 值指示终结点支持的突发事务的最大数目。 在一个服务间隔内,最多可以有三个突发事务 (索引为 0 到 2) 。

    • 终结点配套描述符的 bMaxBurst 字段。 此值指示单个突发事务中可以存在的 wMaxPacketSize 区块数。 突发事务中最多可以有 16 个区块, (索引为 0 到 15) 。

    • wBytesPerInterval 指示主机在总线间隔内可以发送或接收的总字节数。 尽管每个总线间隔的最大字节数可以计算为 (bMaxBurst+1) * (Mult+1) * wMaxPacketSize,但 USB 3.0 规范建议改用 wBytesPerInterval 值。 wBytesPerInterval 值必须小于或等于该计算值。

      重要

      对于客户端驱动程序,前面所述的值仅供信息使用。 驱动程序必须始终使用终结点描述符的 MaximumPacketSize 值来确定传输缓冲区的布局。

  • 终结点多久发送或接收一次数据?

    Interval 成员用于确定终结点可以发送或接收数据的频率。 设备设置该值,客户端驱动程序无法更改该值。 USB 驱动程序堆栈使用另一个数字来确定将常时等量数据包插入数据流的频率:从 Interval 值派生的轮询周期。

    对于全速传输, 间隔 和轮询周期值始终为 1;USB 驱动程序堆栈忽略其他值。

    下表显示了高速和超高速传输的 间隔 和计算轮询周期:

    时间间隔 轮询周期 (2Interval-1)
    1 1;每隔一个总线间隔传输数据。
    2 2;每隔一秒的总线间隔传输一次数据。
    3 4;每隔第四个总线间隔传输一次数据。
    4 8;每隔第八个总线间隔传输一次数据。
  • 每个总线速度的数据包数有哪些限制。

    在 URB 中,对于一个全速设备,最多只能发送 255 个常时等量数据包;适用于高速和超高速设备的 URB 中的 1024 个数据包。 在 URB 中发送的数据包数必须是每个帧中数据包数的倍数。

    轮询期 高速/SuperSpeed 的数据包数
    1 8 的倍数
    2 4 的倍数
    3 2 的倍数
    4 任意

请考虑 wMaxPacketSize 为 1,023 的示例全速终结点。 对于此示例,应用程序提供了 25,575 字节的缓冲区。 该缓冲区的传输需要 25 个常时等量数据包 (25575/1023) 。

请考虑一个高速终结点示例,其终结点描述符中指示了以下特征。

  • wMaxPacketSize 为 1,024。
  • 位 12..11 指示另外两个事务。
  • 间隔 为 1。

客户端驱动程序选择配置后,常时常量管道的 MaximumPacketSize 指示 3,072 字节 (总事务 * wMaxPacketSize) 。 其他事务允许客户端驱动程序在每个微帧中传输 3,072 个字节,在一个帧中传输总共 24,576 个字节。 下图显示了在一个微帧中传输常量数据包的频率,以便进行高速传输。

常时等量传输间隔、轮询周期和数据包的示意图。

请考虑一个示例 SuperSpeed 终结点,其特征在终结点和 SuperSpeed 终结点配套描述符中指示:

  • wMaxPacketSize 为 1,024。
  • bMaxBurst 为 15。
  • 间隔 为 1。
  • Isochronous.Mult 为 2。
  • wBytesPerInterval 为 45000。

在前面的示例中,即使最大字节数可以计算为 wMaxPacketSize * (bMaxBurst +1) * (Mult + 1) 导致 49,152 个字节,设备也会将值限制为 45,000 字节的 wBytesPerInterval 值。 该值也反映在 MaximumPacketSize 45,000 中。 客户端驱动程序只能使用 MaximumPacketSize 值。 在此示例中,请求可分为三个突发事务。 前两个突发事务各包含 16 个 wMaxPacketSize 区块。 最后一个突发事务包含 12 个用于保存剩余字节的区块。 此图显示了轮询间隔以及通过超速传输常量数据包传输的字节数。

超速等量传输间隔、轮询周期和数据包的示意图。

生成常时等量传输的请求:

  1. 获取每个常量数据包的大小。
  2. 确定每帧的常时等量数据包数。
  3. 计算保存整个传输缓冲区所需的常时等量数据包数。
  4. 分配 URB 结构以描述传输的详细信息。
  5. 指定每个常量数据包的详细信息,例如数据包偏移量。

有关发送常量传输请求的完整代码示例,请参阅 USBSAMP。

本主题中的此示例简化了常量传输的 USBSAMP 实现。 该示例计算传输所需的总帧数。 根据可以在帧中发送的数据量,传输缓冲区划分为较小的区块大小字节。

以下过程详细说明了上述步骤,并演示了客户端驱动程序可用于生成和发送高速常时等量终结点的等时等传输请求的计算和例程。 过程中使用的值基于前面所述的示例终结点特征。

步骤 1:获取常量数据包的大小

通过检查管道的 MaximumPacketSize 值来确定常时等量数据包的大小。

对于全速传输,常时等量数据包的大小是可以在一帧中传输的字节数。 对于高速和超高速传输,常量数据包的大小是可在一个微帧中传输的字节总数。 这些值在管道的 MaximumPacketSize 中指示。

在示例中, MaximumPacketSize 为每帧 1023 字节, (全速) ;每个微帧 3072 字节 (高速) ;每个微帧 45,000 字节 (SuperSpeed) 。

注意

MaximumPacketSize 值指示常量数据包允许的最大大小。 客户端驱动程序可以将每个常量数据包的大小设置为小于 MaximumPacketSize 值的任何值。

步骤 2:确定每帧常时等量数据包数

对于全速传输,在每个帧中传输一个常量数据包。

对于高速和超高速传输,此值必须派生自 Interval 值。 在示例中,间隔为 1。 因此,常时等量数据包数必须为每帧 8 个。 有关其他间隔值,请参阅先决条件部分中的表。

步骤 3:计算保存整个传输缓冲区所需的常时等量数据包数

计算传输整个缓冲区所需的常时等量数据包数。 此值可以通过将传输缓冲区的长度除以常时等量数据包的大小来计算。

在此示例中,我们假设每个常时等量数据包的大小为 MaximumPacketSize ,传输缓冲区长度是 MaximumPacketSize 值的倍数。

例如,对于全速传输,提供的 25,575 字节的缓冲区需要 25 个常量数据包 (25575/1023) 。 对于高速传输,大小为 24,576 的缓冲区分为 8 个常量数据包, (24576 /3072) 进行传输。 对于 SuperSpeed,大小为 360,000 字节的缓冲区适合 8 个常量数据包, (360000/45000) 。

客户端驱动程序应验证以下要求:

  • 常时等量数据包数必须是每帧数据包数的倍数。
  • 对于全速设备,进行传输所需的常时等量数据包的最大数目不得超过 255 个;1024,适用于高速或超高速设备。

步骤 4:分配 URB 结构以描述传输的详细信息

  1. 在非分页池中分配 URB 结构。

    如果客户端驱动程序使用 WDM 例程,则驱动程序必须调用USBD_IsochUrbAllocate(如果你有适用于Windows 8的 Windows 驱动程序工具包 (WDK) )。 客户端驱动程序可以使用 例程来面向 Windows Vista 和更高版本的 Windows 操作系统。 如果没有用于Windows 8的 WDK,或者客户端驱动程序适用于早期版本的操作系统,可以通过调用 ExAllocatePoolWithTag 在堆栈上或非分页池中分配结构。

    WDF 客户端驱动程序可以调用 WdfUsbTargetDeviceCreateIsochUrb 方法,为 URB 结构分配内存。

  2. URB 结构的 UrbIsochronousTransfer 成员指向描述常量传输详细信息的 _URB_ISOCH_TRANSFER 结构。 按如下所示初始化以下 UrbIsochronousTransfer 成员:

    • UrbIsochronousTransfer.Hdr.Length 成员设置为 URB 的大小。 若要获取 URB 的大小,请调用 GET_ISO_URB_SIZE 宏并指定数据包数。

    • UrbIsochronousTransfer.Hdr.Function 成员设置为 URB_FUNCTION_ISOCH_TRANSFER

    • UrbIsochronousTransfer.NumberOfPackets 成员设置为常量数据包数。

    • UrbIsochronousTransfer.PipeHandle 设置为与终结点关联的管道的不透明句柄。 确保管道句柄是通用串行总线 (USB) 驱动程序堆栈使用的 USBD 管道句柄。

      若要获取 USBD 管道句柄,WDF 客户端驱动程序可以调用 WdfUsbTargetPipeWdmPipeHandle 方法,并将 WDFUSBPIPE 句柄指定为框架的管道对象。 WDM 客户端驱动程序必须使用在 USBD_PIPE_INFORMATION 结构的 PipeHandle 成员中获取的相同句柄。

    • 指定传输方向。 将 UrbIsochronousTransfer.TransferFlags 设置为 USBD_TRANSFER_DIRECTION_IN,以便从设备) 读取常时等量 IN 传输 (; (写入设备) ,USBD_TRANSFER_DIRECTION_OUT常量 OUT 传输。

    • UrbIsochronousTransfer 中指定USBD_START_ISO_TRANSFER_ASAP标志。TransferFlags。 标志指示 USB 驱动程序堆栈在下一个适当的帧中发送传输。 客户端驱动程序首次为此管道发送常时等量 URB 时,驱动程序堆栈会尽快在 URB 中发送常时等量数据包。 USB 驱动程序堆栈跟踪要用于该管道上的后续 URL 的下一帧。 如果在发送使用 USBD_START_ISO_TRANSFER_ASAP 标志的后续常时常量 URB 时出现延迟,驱动程序堆栈会将该 URB 的部分或所有数据包视为延迟,并且不会传输这些数据包。

      如果堆栈在完成该管道的上一个 URB 后未收到 1024 帧的常量 URB,USB 驱动程序堆栈将重置其USBD_START_ISO_TRANSFER_ASAP启动帧跟踪。 可以指定起始帧,而不是指定USBD_START_ISO_TRANSFER_ASAP标志。 有关详细信息,请参见“备注”部分。

    • 指定传输缓冲区及其大小。 可以在 UrbIsochronousTransfer.TransferBuffer 或用于描述 UrbIsochronousTransfer.TransferBufferMDL 中的缓冲区的 MDL 中设置指向缓冲区的指针。

      若要检索传输缓冲区的 MDL,WDF 客户端驱动程序可以调用 WdfRequestRetrieveOutputWdmMdlWdfRequestRetrieveInputWdmMdl,具体取决于传输的方向。

步骤 5:指定传输中每个常时等量数据包的详细信息

USB 驱动程序堆栈分配的新 URB 结构足够大,用于保存有关每个常时等量数据包的信息,但不包含数据包中包含的数据。 在 URB 结构中, UrbIsochronousTransfer.IsoPacket 成员是 一个USBD_ISO_PACKET_DESCRIPTOR 数组,用于描述传输中每个常时等量数据包的详细信息。 数据包必须是连续的。 数组中的元素数必须是 URB 的 UrbIsochronousTransfer.NumberOfPackets 成员中指定的常时等量数据包数。

对于高速传输,数组中的每个元素都与一个微帧中的一个常量数据包相关联。 对于全速,每个元素都与一帧中传输的一个常量数据包相关联。

对于每个元素,指定从请求的整个传输缓冲区开始的每个常时等量数据包的字节偏移量。 可以通过设置 UrbIsochronousTransfer.IsoPacket[i] 来指定该值。偏移 成员。 USB 驱动程序堆栈使用指定的值来跟踪要发送或接收的数据量。

设置 Full-Speed 传输的偏移量

例如,这些是全速传输缓冲区的数组条目。 在全速模式下,客户端驱动程序有一个帧用于传输一个常量数据包,最大为 1,023 个字节。 25,575 字节的传输缓冲区可以容纳 25 个常时等量数据包,每个数据包长度为 1,023 个字节。 整个缓冲区总共需要 25 帧。

Frame 1 IsoPacket [0].Offset = 0 (start address)
Frame 2 IsoPacket [1].Offset = 1023
Frame 3 IsoPacket [2].Offset = 2046
Frame 4 IsoPacket [3].Offset = 3069
...
Frame 25 IsoPacket [24].Offset = 24552

Total length transferred is 25,575 bytes.

设置 High-Speed 传输的偏移量

例如,这些是高速传输缓冲区的数组条目。 该示例假定缓冲区为 24,576 字节,客户端驱动程序有一个帧用于传输 8 个常量数据包,每个包长 3,072 个字节。

Microframe 1 IsoPacket [0].Offset = 0 (start address)
Microframe 2 IsoPacket [1].Offset = 3072
Microframe 3 IsoPacket [2].Offset = 6144
Microframe 4 IsoPacket [3].Offset = 9216
Microframe 5 IsoPacket [4].Offset = 12288
Microframe 6 IsoPacket [5].Offset = 15360
Microframe 7 IsoPacket [6].Offset = 18432
Microframe 8 IsoPacket [7].Offset = 21504

Total length transferred is 24,576 bytes.

设置超速传输的偏移量

例如,这是 SuperSpeed 的数组偏移量。 一个帧中最多可以传输 45,000 个字节。 大小为 360,000 的传输缓冲区适合 8 个微帧。

Microframe 1 IsoPacket [0].Offset = 0 (start address)
Microframe 2 IsoPacket [1].Offset = 45000
Microframe 3 IsoPacket [2].Offset = 90000
Microframe 4 IsoPacket [3].Offset = 135000
Microframe 5 IsoPacket [4].Offset = 180000
Microframe 6 IsoPacket [5].Offset = 225000
Microframe 7 IsoPacket [6].Offset = 270000
Microframe 8 IsoPacket [7].Offset = 315000

Total length transferred is 360,000 bytes.

UrbIsochronousTransfer.IsoPacket[i]。Length 成员并不表示常时等量 URB 的每个数据包的长度。 IsoPacket[i]。长度 由 USB 驱动程序堆栈更新,以指示从设备接收的实际字节数,用于常量 IN 传输。 对于常时等量 OUT 传输,驱动程序堆栈会忽略 IsoPacket[i] 中设置的值。长度

指定传输的起始 USB 帧编号

URB 的 UrbIsochronousTransfer.StartFrame 成员指定传输的起始 USB 帧编号。 客户端驱动程序提交 URB 的时间和 USB 驱动程序堆栈处理 URB 的时间之间始终存在延迟。 因此,客户端驱动程序应始终指定一个晚于驱动程序提交 URB 时当前帧的起始帧。 若要检索当前帧编号,客户端驱动程序可以将URB_FUNCTION_GET_CURRENT_FRAME_NUMBER请求发送到 USB 驱动程序堆栈 (_URB_GET_CURRENT_FRAME_NUMBER) 。

对于常时等量传输,当前帧与 StartFrame 值之间的绝对差必须小于 USBD_ISO_START_FRAME_RANGE。 如果 StartFrame 不在适当的范围内,则 USB 驱动程序堆栈会将 URB 标头的状态成员 (看到 _URB_HEADER) 设置为USBD_STATUS_BAD_START_FRAME并放弃整个 URB。

URB 中指定的 StartFrame 值指示传输 URB 的第一个常时等量数据包的帧编号。 后续数据包的帧编号取决于终结点的总线速度和轮询周期值。 例如,对于全速传输,第一个数据包在 StartFrame 中传输;第二个数据包在 StartFrame+1 中传输,依此类移。 USB 驱动程序堆栈以全速度以帧为单位传输常时等量数据包的方式如下所示:

Frame (StartFrame)   IsoPacket [0]
Frame (StartFrame+1) IsoPacket [1]
Frame (StartFrame+2) IsoPacket [2]
Frame (StartFrame+3) IsoPacket [3]
...

对于间隔值为 1 的高速设备,帧编号每八个微帧更改一次。 USB 驱动程序堆栈在帧中高速传输等时等量数据包的方式如下所示:

Frame (StartFrame) Microframe 1 IsoPacket [0]
...
Frame (StartFrame) Microframe 8 IsoPacket [7]
Frame (StartFrame+1) Microframe 1 IsoPacket [8]
...
Frame (StartFrame+1) Microframe 8 IsoPacket [15]
Frame (StartFrame+2) Microframe 1 IsoPacket [16]
...
Frame (StartFrame+2) Microframe 8 IsoPacket [23]

当 USB 驱动程序堆栈处理 URB 时,驱动程序会丢弃 URB 中帧数低于当前帧号的所有常时等量数据包。 驱动程序堆栈将每个丢弃数据包的数据包描述符 的状态 成员设置为USBD_STATUS_ISO_NA_LATE_USBPORT、USBD_STATUS_ISO_NOT_ACCESSED_BY_HW或USBD_STATUS_ISO_NOT_ACCESSED_LATE。 即使丢弃了 URB 中的某些数据包,驱动程序堆栈也会尝试仅传输帧号高于当前帧号的数据包。

有效 StartFrame 成员的检查在高速传输中稍微复杂一些,因为 USB 驱动程序堆栈将每个常量数据包加载到高速微帧中;但是,StartFrame 中的值是指 1 毫秒 (全速) 帧数,而不是微帧。 例如,如果 URB 中记录的 StartFrame 值比当前帧少一个,则驱动程序堆栈可以丢弃多达 8 个数据包。 丢弃的数据包的确切数量取决于与常时等量管道关联的轮询周期。

等时等量传输示例

下面的代码示例演示如何为全速、高速和超高速传输的常时等量传输创建 URB。

#define MAX_SUPPORTED_PACKETS_FOR_HIGH_OR_SUPER_SPEED 1024
#define MAX_SUPPORTED_PACKETS_FOR_FULL_SPEED 255

NTSTATUS CreateIsochURB  ( PDEVICE_OBJECT         DeviceObject,
                          PUSBD_PIPE_INFORMATION  PipeInfo,
                          ULONG                   TotalLength,
                          PMDL                    RequestMDL,
                          PURB                    Urb)
{
    PDEVICE_EXTENSION        deviceExtension;
    ULONG                    numberOfPackets;
    ULONG                    numberOfFrames;
    ULONG                    isochPacketSize = 0;
    ULONG                    transferSizePerFrame;
    ULONG                    currentFrameNumber;
    size_t                   urbSize;
    ULONG                    index;
    NTSTATUS                 ntStatus;

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    isochPacketSize = PipeInfo->MaximumPacketSize;

    // For high-speed transfers
    if (deviceExtension->IsDeviceHighSpeed || deviceExtension->IsDeviceSuperSpeed)
    {
        // Ideally you can pre-calculate numberOfPacketsPerFrame for the Pipe and
        // store it in the pipe context.

        switch (PipeInfo->Interval)
        {
        case 1:
            // Transfer period is every microframe (eight times a frame).
            numberOfPacketsPerFrame = 8;
            break;

        case 2:
            // Transfer period is every 2 microframes (four times a frame).
            numberOfPacketsPerFrame = 4;
            break;

        case 3:
            // Transfer period is every 4 microframes (twice in a frame).
            numperOfPacketsPerFrame = 2;
            break;

        case 4:
        default:
            // Transfer period is every 8 microframes (once in a frame).
            numberOfPacketsPerFrame = 1;
            break;
        }

        //Calculate the number of packets.
        numberOfPackets = TotalLength / isochPacketSize;

        if (numberOfPackets > MAX_SUPPORTED_PACKETS_FOR_HIGH_OR_SUPER_SPEED)
        {
            // Number of packets cannot be  greater than 1021.
            ntStatus = STATUS_INVALID_PARAMETER;
            goto Exit;
        }

        if (numberOfPackets % numberOfPacketsPerFrame != 0)
        {

            // Number of packets should be a multiple of numberOfPacketsPerFrame
            ntStatus = STATUS_INVALID_PARAMETER;
            goto Exit;
        }

    }
    else if (deviceExtension->IsDeviceFullSpeed)
    {
        //For full-speed transfers
        // Microsoft USB stack only supports bInterval value of 1 for
        // full-speed isochronous endpoints.

        //Calculate the number of packets.
        numberOfPacketsPerFrame = 1;

        numberOfPackets = TotalLength / isochPacketSize;

        if (numberOfPackets > MAX_SUPPORTED_PACKETS_FOR_FULL_SPEED)
        {
            // Number of packets cannot be greater than 255.
            ntStatus = STATUS_INVALID_PARAMETER;
            goto Exit;
        }
    }

    // Allocate an isochronous URB for the transfer
    ntStatus = USBD_IsochUrbAllocate (deviceExtension->UsbdHandle,
        numberOfPackets,
        &Urb);

    if (!NT_SUCCESS(ntStatus))
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    urbSize = GET_ISO_URB_SIZE(numberOfPackets);

    Urb->UrbIsochronousTransfer.Hdr.Length = (USHORT) urbSize;
    Urb->UrbIsochronousTransfer.Hdr.Function = URB_FUNCTION_ISOCH_TRANSFER;
    Urb->UrbIsochronousTransfer.PipeHandle = PipeInfo->PipeHandle;

    if (USB_ENDPOINT_DIRECTION_IN(PipeInfo->EndpointAddress))
    {
        Urb->UrbIsochronousTransfer.TransferFlags = USBD_TRANSFER_DIRECTION_IN;
    }
    else
    {
        Urb->UrbIsochronousTransfer.TransferFlags = USBD_TRANSFER_DIRECTION_OUT;
    }

    Urb->UrbIsochronousTransfer.TransferBufferLength = TotalLength;
    Urb->UrbIsochronousTransfer.TransferBufferMDL = RequestMDL;
    Urb->UrbIsochronousTransfer.NumberOfPackets = numberOfPackets;
    Urb->UrbIsochronousTransfer.UrbLink = NULL;

    // Set the offsets for every packet for reads/writes

    for (index = 0; index < numberOfPackets; index++)
    {
        Urb->UrbIsochronousTransfer.IsoPacket[index].Offset = index * isochPacketSize;
    }

    // Length is a return value for isochronous IN transfers.
    // Length is ignored by the USB driver stack for isochronous OUT transfers.

    Urb->UrbIsochronousTransfer.IsoPacket[index].Length = 0;
    Urb->UrbIsochronousTransfer.IsoPacket[index].Status = 0;

    // Set the USBD_START_ISO_TRANSFER_ASAP. The USB driver stack will calculate the start frame.
    // StartFrame value set by the client driver is ignored.
    Urb->UrbIsochronousTransfer.TransferFlags |= USBD_START_ISO_TRANSFER_ASAP;

Exit:

    return ntStatus;
}