USB 主机维护者注意事项（DWC_OTG 控制器）
=========================================

:link_to_translation:`en:[English]`

{IDF_TARGET_NAME} 使用 DesignWare USB 2.0 On-the-Go 控制器（后文简称为 DWC_OTG）作为其底层硬件控制器。启用分散/聚集式 DMA 功能后，DWC_OTG 将以主机模式运行。

.. note::

    本节高度概括了 DWC_OTG 在主机模式下的操作。有关 DWC_OTG 的详细信息，请参阅 DWC_OTG 技术规格书和编程指南。

主机模式操作模型
----------------

下图展示了 DWC_OTG 在主机模式下的简化操作模型。该图包含了 DWC_OTG 主机模式下的一些关键概念及术语。

.. figure:: ../../../../_static/usb_host/dwc-otg-operation.png
    :align: center
    :alt: DWC_OTG 主机模式操作模型
    :figclass: align-center

.. note::

    详情请参阅技术规格书第 2.1.4 节（主机架构）。

主机端口
^^^^^^^^^

主机端口指的是 DWC_OTG 提供的单个 USB 端口（在 USB 术语中，也被视为总线根集线器的单个 USB 端口）。主机端口通常只能连接一个设备，但可以通过集线器连接更多设备。

主机端口具有以下功能：

- 检测直连设备的连接/断开情况。
- 检测直连设备的速度。
- 发送各种总线信号（如挂起、恢复、复位）。

主机通道
^^^^^^^^

在主机模式下，DWC_OTG 使用通道与设备端点通信，每个通道会映射到特定的端点（在 USB 术语中，通道是管道的硬件表示）。例如，有个通道将映射到 EP 1 OUT。每个通道都有一组控制和状态寄存器 (CSR)，可以进行独立配置和控制，主要用于：

- 指定通道目标端点的详细信息（例如，设备地址、端点编号、传输类型、通道方向）。
- 启动通道传输（如设置 DMA 描述符）。

使用分散/聚集式 DMA 功能时，主机通道上的传输完全由事件驱动，用户只需填写恰当的 DMA 描述符，填充通道的 CSR，然后启用通道即可。传输完成后，通道将生成中断。

数据 FIFO
^^^^^^^^^^

主机模式下，DWC_OTG 使用多个 FIFO 作为 USB 传输数据负载的暂存区。使用 DMA 时，DMA 引擎将在 TX/RX FIFO 和 {IDF_TARGET_NAME} 的内部存储器之间复制数据：

- 对于 OUT 传输，数据负载将由 DMA 从主内存复制到某个 TX FIFO 中。MAC 层将按照 USB 数据包格式传输该数据负载。
- 对于 IN 传输，MAC 层会解析接收到的 USB 数据包，并将接收的数据负载存储在 RX FIFO 中。之后，由 DMA 将数据复制到主内存中。

目标 FIFO 由通道的方向和传输类型决定：

- 所有 IN 通道数据进入 RX FIFO。
- 所有非周期性（即控制和批量）OUT 通道数据进入非周期性 TX (NPTX) FIFO。
- 所有周期性（即中断和同步）OUT 通道数据进入周期性 TX (PTX) FIFO。

.. note::

    非周期性和周期性 OUT 通道数据应分别进入 NPTX 和 PTX FIFO，因为中断和同步端点具有周期性特性（由端点的 ``bInterval`` 值指定）。DWC_OTG 会自动调度周期性传输，因此 PTX FIFO 能单独暂存这些周期性传输。

DMA 引擎
^^^^^^^^

DMA 引擎负责在 FIFO 和主内存之间复制数据。启用主机模式分散/聚集式 DMA 功能，无需软件干预，特定通道即可自动执行多个传输。下图展示了 DWC_OTG 主机模式分散/聚集式 DMA 内存结构。

.. figure:: ../../../../_static/usb_host/dwc-otg-scatter-gather.png
    :align: center
    :alt: DWC_OTG 主机模式分散/聚集式 DMA 内存结构
    :figclass: align-center

- USB 传输由队列传输描述符 (QTD) 描述。每个 QTD 包含：

  - 一个 32 位的 buffer 状态四元组，用来指定有关传输的详细信息，且会在传输完成后报告传输状态。buffer 状态四元组可以指定 QTD 在传输完成后生成中断或是停止通道，也指定上述两种情况同时发生。
  - 一个 32 位指针，指向一个包含 OUT 传输数据负载的 buffer，或是指向一个空的 buffer，这个空 buffer 将用来存储 IN 传输的数据负载。

- 每个 QTD 的数据负载可以大于其目标端点的最大数据包大小 (MPS)。DWC_OTG 能够自动拆分传输。
- 多个 QTD 可以放入一个 QTD 列表中。通道将自动执行列表中的每个 QTD，并可设置是否要循环执行。
- 在通道开始传输数据之前，会为其配置 QTD 列表（以及 QTD 列表长度）。一旦启用通道，DWC_OTG 将自动开始 USB 传输。
- 在特定 QTD 或整个 QTD 列表结束任务后，通道将生成中断（可配置）。

.. note::

    详情请参阅编程指南第 6.2.1 节（描述符内存结构）。

硬件配置
--------

DWC_OTG IP 是可配置的。有关 {IDF_TARGET_NAME} 的 DWC_OTG 的重要主机配置，请参阅下表：

.. only:: esp32p4

    .. list-table:: {IDF_TARGET_NAME} 的 DWC_OTG 配置
        :widths: 70 30
        :header-rows: 1

        * - 描述
          - 配置
        * - 支持 OTG 的主机和设备模式
          - ``OTG_MODE = 0``
        * - 支持高速 (HS)、全速 (FS) 和低速 (LS)
          - ``OTG_FSPHY_INTERFACE = 2``、``OTG_HSPHY_INTERFACE = 3``
        * - 支持分散/聚集式 DMA 功能的内部 DMA 控制器
          - ``OTG_ARCHITECTURE = 2``、 ``OTG_EN_DESC_DMA = 1``
        * - 不支持分割传输
          - ``OTG_SINGLE_POINT = 1``
        * - 16 个主机模式通道
          - ``OTG_NUM_HOST_CHAN = 16``
        * - 支持包括 ISOC 和 INTR OUT 传输在内的所有传输类型
          - ``OTG_EN_PERIO_HOST = 1``
        * - 动态大小的 4096 字节（1024 行）数据 FIFO
          - ``OTG_DFIFO_DYNAMIC = 1``、``OTG_DFIFO_DEPTH = 1024``
        * - 每个微帧仅支持 4 个周期性和 4 个非周期性事务
          - ``OTG_NPERIO_TX_QUEUE_DEPTH = 4``、``OTG_PERIO_TX_QUEUE_DEPTH = 4``

.. only:: esp32s2 or esp32s3

    .. list-table:: {IDF_TARGET_NAME} 的 DWC_OTG 配置
        :widths: 70 30
        :header-rows: 1

        * - 描述
          - 配置
        * - 支持 OTG 的主机和设备模式
          - ``OTG_MODE = 0``
        * - 支持全速 (FS) 和低速 (LS)
          - ``OTG_FSPHY_INTERFACE = 1``、``OTG_HSPHY_INTERFACE = 0``
        * - 支持分散/聚集式 DMA 功能的内部 DMA 控制器
          - ``OTG_ARCHITECTURE = 2``、 ``OTG_EN_DESC_DMA = 1``
        * - 8 个主机模式通道
          - ``OTG_NUM_HOST_CHAN = 8``
        * - 支持包括 ISOC 和 INTR OUT 传输在内的所有传输类型
          - ``OTG_EN_PERIO_HOST = 1``
        * - 动态大小的 1024 字节（256 行）数据 FIFO
          - ``OTG_DFIFO_DYNAMIC = 1``, ``OTG_DFIFO_DEPTH = 256``

分散/聚集式 DMA 传输
---------------------

主机通道传输的基本操作步骤如下：

#. 准备好数据 buffer、QTD 及 QTD 列表，确保有 QTD 能在完成任务后停止通道并生成中断。
#. 通过 CSR 设置通道和端点的特性（如 EP 地址、传输类型、EP MPS 等）。
#. 设置有关通道 QTD 列表的 CSR（如 QTD 列表指针和 QTD 列表长度）及通道中断 CSR。
#. 启用通道。硬件将使用 DMA 自动处理传输。
#. 在发生通道事件（如 QTD 完成任务或通道报错）时，通道将生成中断。
#. 解析通道中断，确定通道事件类型。
#. 解析 QTD，确定每个单独传输的结果。

若传输类型不同，在通道操作和 QTD 列表使用上也会有一些细微差别。

批量传输
^^^^^^^^

批量传输最为简单。每个 QTD 代表特定方向的批量传输，DWC_OTG 会自动将特定 QTD 拆分为多个 MPS 大小的传输。因此，可以用多次批量传输填充一个 QTD 列表，并自动执行整个列表（即，只在最后一个 QTD 完成任务时发送中断）。

控制传输
^^^^^^^^

控制传输是双向的，因而较为复杂（即，每个控制传输阶段可以有不同的方向）。每个阶段需要一个单独的 QTD，并且每个 QTD 必须在完成任务后停止传输通道，从而确保能通过重新配置通道的 CSR 来改变通道的方向。通常来说，控制传输需要 3 个 QTD（每个阶段一个）。

中断传输
^^^^^^^^

根据 USB 2.0 规范，中断传输在端点指定的服务周期（即 ``bInterval``）执行传输事务。特定中断端点在一个服务周期内不得执行多次中断传输。服务周期由微帧或帧的数量指定，因此特定中断端点通常每隔 N 个微帧或帧执行一次传输，直至完成传输。特定中断通道的服务周期（即 ``bInterval``）由主机帧列表指定（详见编程指南第 6.5 节）。

.. note::

  HS USB 允许一个中断端点在一个微帧内执行三次中断传输。详情请参阅 USB 2.0 规范第 5.7.3 节（中断传输数据包大小限制）。

总的来说，主机模式分散/聚集式 DMA 中的中断传输具有以下特点：

- 如果 QTD 数据负载大于端点的 MPS，通道会自动将传输拆分为多个 MPS 大小的传输事务（类似于批量传输）。但每次传输将在端点指定的服务周期内执行（即每个 ``bInterval`` 时间段内执行一次传输），直至完成传输。
- 对于中断 IN 传输，若收到短包（即传输的数据负载小于 MPS），则表明端点已无需要发送的数据，此时：

  - 即便 QTD 未设置 IOC（完成时中断）位，通道也会生成额外的通道中断。
  - 即使生成了额外的通道中断，通道也不会停止。
  - 软件必须使用这个额外的中断手动停止中断通道，从而取消 QTD 列表中剩余的 QTD。


.. note::

  基于上述中断传输的特点，对于软件来说，为每次传输分配一个 QTD 可能比为整个传输分配一个 QTD 更加容易。

同步传输
^^^^^^^^

根据 USB 2.0 规范，同步传输在端点指定的服务周期（即 ``bInterval``）执行传输事务，以实现恒定的数据传输速率。特定同步端点在一个服务周期内不得执行多次中断传输。服务周期由微帧或帧的数量指定，因此特定同步端点通常每隔 N 个微帧或帧执行一次传输，直至完成传输。特定同步通道的服务周期（即 ``bInterval``）由主机帧列表指定（详见编程指南第 6.5 节）。

但与中断传输不同，由于同步传输需要保持恒定的数据传输速率，即便传输失败（或收到 NAK），同步传输也不会重试，

.. note::

  HS USB 允许一个同步端点在一个微帧内执行三次同步传输。详情请参阅 USB 2.0 规范第 5.6.3 节（同步传输数据包大小限制）。

总的来说，主机模式分散/聚集式 DMA 中的同步传输具有以下特点：

- 必须为每微帧或帧分配一个 QTD，但非服务周期的 QTD 应保持空白（即，如果通道的服务周期是每 N 个微帧或帧，则只需填充每第 N 个 QTD）。
- **每个已填充的 QTD 只能代表一次传输事务，而非整个传输过程**。
- 同步传输不会在失败时重试，因此必须检查每个完成任务的 QTD 的状态。

补充说明
--------

某些 DWC_OTG 行为在技术规格书或编程指南中均未提及。本节解释了一些与主机协议栈的实现相关的行为。

端口错误不会触发通道中断
^^^^^^^^^^^^^^^^^^^^^^^^

一个或多个通道运行时，若发生端口错误（例如突然断连或端口过流），则：

- 通道仍可运行（即，保持设置 ``HCCHAR.ChEna``）且不会生成通道中断。
- 理论上可以通过设置 ``HCCHAR.ChDis`` 来禁用通道，但这对同步通道无效，因为通道禁用中断未能生成。

因此，发生端口错误时，应使用控制器软复位以确保禁用所有通道。

端口复位中断
^^^^^^^^^^^^

- DWC_OTG 在其端口上发出复位信号，如果设备在此期间断开连接，则断连中断（即 ``HPRT.PrtConnDet``）不会在取消复位前生成。
- 在复位已经启用的端口（即 ``HPRT.PrtEna``）时，如枚举期间的二次复位或是设备运行期间的复位，无论是在复位信号生效还是失效时，都会生成端口启用/禁用更改中断（即 ``HPRT.PrtEnChng``）。
