Linux驱动 2026年8月4日 35 分钟

USB控制传输SETUP事务与数据方向判断详解1

一、控制传输的三个阶段 USB控制传输(Control Transfer)包含三个阶段: 关键问题:如何通过Setup阶…

一、控制传输的三个阶段

USB控制传输(Control Transfer)包含三个阶段

1. Setup阶段    (必需)  - SETUP令牌 + DATA0数据包 + ACK握手
2. Data阶段     (可选)  - IN/OUT令牌 + DATAx + ACK
3. Status阶段   (必需)  - IN/OUT令牌 + DATA1(长度=0) + ACK

关键问题:如何通过Setup阶段的DATA0包内容,判断后续Data阶段是IN(读)还是OUT(写)?


二、SETUP数据包结构

Setup阶段的DATA0包固定为8字节,格式如下:

偏移  字段名           大小   说明
────────────────────────────────────────────────
0     bmRequestType    1字节  请求类型和方向
1     bRequest         1字节  具体请求代码
2-3   wValue           2字节  请求参数
4-5   wIndex           2字节  索引/选择器
6-7   wLength          2字节  数据阶段的字节数

核心字段

  • bmRequestType – 最高位D7决定数据方向
  • wLength – 决定是否有Data阶段

三、bmRequestType字段详解(关键!)

3.1 字段结构

bmRequestType (1字节 = 8位)

 D7    D6 D5   D4 D3 D2 D1 D0
 │     └─┬─┘   └──────┬──────┘
 │       │            │
 │       │            └─ Recipient (接收者)
 │       │                 00000 = Device (设备)
 │       │                 00001 = Interface (接口)
 │       │                 00010 = Endpoint (端点)
 │       │                 00011 = Other (其它)
 │       │
 │       └─ Type (请求类型)
 │            00 = Standard (标准请求)
 │            01 = Class (类请求)
 │            10 = Vendor (厂商请求)
 │            11 = Reserved (保留)
 │
 └─ Direction (数据方向) ← **这是关键位!**
      0 = Host-to-Device (OUT, 主机→设备, 写)
      1 = Device-to-Host (IN,  设备→主机, 读)

3.2 方向判断规则

D7 = 0(主机到设备)

Data阶段使用 OUT 令牌
主机 --OUT令牌--> 设备
主机 --DATAx--> 设备
设备 --ACK--> 主机

Status阶段使用 IN 令牌(反向握手)
主机 --IN令牌--> 设备
设备 --DATA1(0字节)--> 主机
主机 --ACK--> 设备

D7 = 1(设备到主机)

Data阶段使用 IN 令牌
主机 --IN令牌--> 设备
设备 --DATAx--> 主机
主机 --ACK--> 设备

Status阶段使用 OUT 令牌(反向握手)
主机 --OUT令牌--> 设备
主机 --DATA1(0字节)--> 设备
设备 --ACK--> 主机

D7 = 无关紧要(wLength = 0)

如果wLength = 0,没有Data阶段
直接进入Status阶段,使用 IN 令牌

四、常见请求示例

4.1 GET_DESCRIPTOR(读设备描述符)

SETUP数据包内容

偏移  字段              十六进制   二进制            说明
───────────────────────────────────────────────────────────
0     bmRequestType    0x80       10000000         D7=1 → Device-to-Host
                                  │└─┬─┘└──┬──┘
                                  │  00   00000
                                  │  标准  设备
                                  └─ IN方向

1     bRequest         0x06                        GET_DESCRIPTOR
2-3   wValue           0x0100                      描述符类型=Device(1)
4-5   wIndex           0x0000                      索引=0
6-7   wLength          0x0012                      请求18字节

后续流程

Setup阶段:
  主机 --SETUP令牌--> 设备
  主机 --DATA0[80 06 01 00 00 00 12 00]--> 设备
  设备 --ACK--> 主机

Data阶段(因为D7=1,使用IN):
  主机 --IN令牌--> 设备
  设备 --DATA1[12 01 00 02 ...]--> 主机  (设备描述符18字节)
  主机 --ACK--> 设备

Status阶段(反向,使用OUT):
  主机 --OUT令牌--> 设备
  主机 --DATA1[](0字节)--> 设备
  设备 --ACK--> 主机

4.2 SET_ADDRESS(设置设备地址)

SETUP数据包内容

偏移  字段              十六进制   二进制            说明
───────────────────────────────────────────────────────────
0     bmRequestType    0x00       00000000         D7=0 → Host-to-Device
                                  │└─┬─┘└──┬──┘
                                  │  00   00000
                                  │  标准  设备
                                  └─ OUT方向(但wLength=0)

1     bRequest         0x05                        SET_ADDRESS
2-3   wValue           0x0003                      新地址=3
4-5   wIndex           0x0000                      索引=0
6-7   wLength          0x0000                      无数据阶段!

后续流程

Setup阶段:
  主机 --SETUP令牌--> 设备
  主机 --DATA0[00 05 03 00 00 00 00 00]--> 设备
  设备 --ACK--> 主机

Data阶段:
  (无,因为wLength = 0)

Status阶段(wLength=0时默认使用IN):
  主机 --IN令牌--> 设备
  设备 --DATA1[](0字节)--> 主机
  设备 --ACK--> 主机
  
设备在Status完成后切换到新地址3

4.3 SET_CONFIGURATION(设置配置)

SETUP数据包内容

偏移  字段              十六进制   二进制            说明
───────────────────────────────────────────────────────────
0     bmRequestType    0x00       00000000         D7=0 → Host-to-Device
1     bRequest         0x09                        SET_CONFIGURATION
2-3   wValue           0x0001                      配置值=1
4-5   wIndex           0x0000
6-7   wLength          0x0000                      无数据阶段

后续流程

Setup阶段:
  主机 --SETUP令牌--> 设备
  主机 --DATA0[00 09 01 00 00 00 00 00]--> 设备
  设备 --ACK--> 主机

Data阶段:
  (无)

Status阶段:
  主机 --IN令牌--> 设备
  设备 --DATA1[](0字节)--> 主机
  主机 --ACK--> 设备

4.4 厂商自定义请求(写数据到设备)

SETUP数据包内容

偏移  字段              十六进制   二进制            说明
───────────────────────────────────────────────────────────
0     bmRequestType    0x40       01000000         D7=0 → Host-to-Device
                                  │└─┬─┘└──┬──┘    D6-5=10 → Vendor
                                  │  10   00000    D4-0=00000 → Device
                                  └─ OUT方向

1     bRequest         0xA0                        厂商自定义命令
2-3   wValue           0x1234                      参数
4-5   wIndex           0x0000
6-7   wLength          0x0004                      要写4字节

后续流程

Setup阶段:
  主机 --SETUP令牌--> 设备
  主机 --DATA0[40 A0 34 12 00 00 04 00]--> 设备
  设备 --ACK--> 主机

Data阶段(因为D7=0且wLength>0,使用OUT):
  主机 --OUT令牌--> 设备
  主机 --DATA1[AA BB CC DD]--> 设备  (4字节数据)
  设备 --ACK--> 主机

Status阶段(反向,使用IN):
  主机 --IN令牌--> 设备
  设备 --DATA1[](0字节)--> 主机
  主机 --ACK--> 设备

4.5 厂商自定义请求(从设备读数据)

SETUP数据包内容

偏移  字段              十六进制   二进制            说明
───────────────────────────────────────────────────────────
0     bmRequestType    0xC0       11000000         D7=1 → Device-to-Host
                                  │└─┬─┘└──┬──┘    D6-5=10 → Vendor
                                  │  10   00000    D4-0=00000 → Device
                                  └─ IN方向

1     bRequest         0xA1                        厂商自定义命令
2-3   wValue           0x5678
4-5   wIndex           0x0000
6-7   wLength          0x0010                      要读16字节

后续流程

Setup阶段:
  主机 --SETUP令牌--> 设备
  主机 --DATA0[C0 A1 78 56 00 00 10 00]--> 设备
  设备 --ACK--> 主机

Data阶段(因为D7=1,使用IN):
  主机 --IN令牌--> 设备
  设备 --DATA1[...16字节...]--> 主机
  主机 --ACK--> 设备

Status阶段(反向,使用OUT):
  主机 --OUT令牌--> 设备
  主机 --DATA1[](0字节)--> 设备
  设备 --ACK--> 主机

五、判断流程总结

5.1 设备端判断逻辑

void handle_setup_packet(uint8_t *setup_data) {
    uint8_t bmRequestType = setup_data[0];
    uint8_t bRequest = setup_data[1];
    uint16_t wValue = *(uint16_t *)&setup_data[2];
    uint16_t wIndex = *(uint16_t *)&setup_data[4];
    uint16_t wLength = *(uint16_t *)&setup_data[6];
    
    // 判断数据方向
    bool is_device_to_host = (bmRequestType & 0x80);  // D7位
    
    if (wLength == 0) {
        // 无数据阶段,直接进入Status
        // Status使用IN令牌(设备发送0字节DATA1)
        prepare_status_in();
    }
    else if (is_device_to_host) {
        // Data阶段:设备→主机(IN)
        // Status阶段:主机→设备(OUT,反向握手)
        prepare_data_in(wLength);
        prepare_status_out();
    }
    else {
        // Data阶段:主机→设备(OUT)
        // Status阶段:设备→主机(IN,反向握手)
        prepare_data_out(wLength);
        prepare_status_in();
    }
    
    // 根据bRequest执行具体操作
    switch (bRequest) {
        case 0x06: handle_get_descriptor(wValue, wIndex, wLength); break;
        case 0x05: handle_set_address(wValue); break;
        case 0x09: handle_set_configuration(wValue); break;
        // ...
    }
}

5.2 判断流程图

收到SETUP令牌和DATA0包
        ↓
解析bmRequestType字段
        ↓
    检查wLength
        ↓
   ┌────┴────┐
   │wLength=0│
   └────┬────┘
        ↓
     无Data阶段
        ↓
   Status用IN
   
   ┌────┴────┐
   │wLength>0│
   └────┬────┘
        ↓
   检查D7位
        ↓
    ┌───┴───┐
    │  D7?  │
    └┬─────┬┘
     │     │
    0│     │1
     │     │
  OUT方向 IN方向
     │     │
Data用OUT Data用IN
Status用IN Status用OUT

六、批量传输(Bulk Transfer)的方向判断

批量传输不使用SETUP,方向由端点描述符决定:

6.1 端点描述符中的方向位

bEndpointAddress (1字节)

 D7    D6 D5 D4   D3 D2 D1 D0
 │     └──┬───┘   └────┬────┘
 │        │            │
 │        │            └─ Endpoint Number (端点号 0-15)
 │        └─ Reserved (保留,必须为0)
 │
 └─ Direction (方向)
      0 = OUT (主机→设备)
      1 = IN  (设备→主机)

6.2 示例

端点描述符

偏移  字段              值         说明
──────────────────────────────────────────
0     bLength          0x07       描述符长度
1     bDescriptorType  0x05       ENDPOINT类型
2     bEndpointAddress 0x81       D7=1 → IN端点
                                  D3-0=1 → 端点1
3     bmAttributes     0x02       Bulk传输
4-5   wMaxPacketSize   0x0040     最大包64字节
6     bInterval        0x00       轮询间隔(批量传输忽略)

传输方向

bEndpointAddress = 0x81
D7 = 1 → IN端点
端点1用于 设备→主机 的批量传输

实际传输:
  主机 --IN令牌[EP1]--> 设备
  设备 --DATA0/DATA1[...]--> 主机
  主机 --ACK--> 设备

对比

bEndpointAddress = 0x02
D7 = 0 → OUT端点
端点2用于 主机→设备 的批量传输

实际传输:
  主机 --OUT令牌[EP2]--> 设备
  主机 --DATA0/DATA1[...]--> 设备
  设备 --ACK--> 主机

七、完整示例:GET_DESCRIPTOR的三个阶段

7.1 Setup阶段

时刻T0:主机发送SETUP令牌
  令牌包:SETUP + Addr=0 + EP=0 + CRC5

时刻T1:主机发送DATA0包
  DATA0包:SYNC + PID(DATA0) +
           [80 06 01 00 00 00 12 00] +  ← 8字节Setup数据
           CRC16 + EOP
  
  解析:
    bmRequestType = 0x80  (10000000)
      D7=1 → Device-to-Host (IN方向)
      D6-5=00 → Standard请求
      D4-0=00000 → Device
    bRequest = 0x06 (GET_DESCRIPTOR)
    wValue = 0x0100 (Device描述符)
    wIndex = 0x0000
    wLength = 0x0012 (18字节)

时刻T2:设备发送ACK
  握手包:SYNC + PID(ACK) + EOP

7.2 Data阶段(IN方向)

时刻T3:主机发送IN令牌(因为D7=1)
  令牌包:IN + Addr=0 + EP=0 + CRC5

时刻T4:设备发送DATA1包(第一个数据包用DATA1)
  DATA1包:SYNC + PID(DATA1) +
           [12 01 00 02 00 00 00 40 ...] +  ← 设备描述符18字节
           CRC16 + EOP

时刻T5:主机发送ACK
  握手包:SYNC + PID(ACK) + EOP

7.3 Status阶段(反向,OUT)

时刻T6:主机发送OUT令牌(反向握手)
  令牌包:OUT + Addr=0 + EP=0 + CRC5

时刻T7:主机发送DATA1包(0字节)
  DATA1包:SYNC + PID(DATA1) +
           [] +  ← 0字节数据
           CRC16 + EOP

时刻T8:设备发送ACK
  握手包:SYNC + PID(ACK) + EOP

控制传输完成!

八、关键要点总结

8.1 方向判断核心

判断依据位置含义
bmRequestType.D7Setup包[0]的最高位0=OUT写, 1=IN读
wLengthSetup包[6-7]0=无Data阶段, >0=有Data阶段
bEndpointAddress.D7端点描述符[2]的最高位批量/中断传输方向

8.2 三阶段方向组合

bmRequestType.D7wLengthData阶段Status阶段
0 (OUT)0IN
0 (OUT)>0OUTIN
1 (IN)>0INOUT
1 (IN)0IN

8.3 记忆技巧

  1. Setup包的D7位决定Data方向
  2. Status阶段总是反向(除了wLength=0,默认IN)
  3. 批量/中断传输看端点描述符的D7位
  4. 等时传输无握手包,只看端点描述符D7位

完成! 这份笔记详细解释了如何通过SETUP数据包的bmRequestType字段判断后续Data阶段的方向。

上一篇 USB子系统3-协议层数据格式

上图为一次数据传输的完整过程,线发送令牌包表示自己要发数据了,并给出地址和端点,之后发出数据包并发出数据,最后设备给出答...

下一篇 USB子系统4-USB设备描述符