USB Descriptor Overview là gì?

Cấu trúc phân cấp USB descriptor: Device → Configuration → Interface → Endpoint. wTotalLength, bDescriptorType, cách host đọc từng bước và điểm dễ gây enumeration fail.

Cập nhật 5 phút đọc
Đọc bằng Tiếng Việt English
USB cover

Descriptor là tập hợp các struct mà device dùng để tự giới thiệu với host trong quá trình enumeration. Host không biết gì về device trước khi đọc descriptor — class, speed, số endpoint, loại transfer — tất cả đều lấy từ đây.

Nếu chỉ đọc spec lần đầu, các descriptor này trông như nhiều struct rời rạc. Nhưng trong lúc debug, nên nhìn chúng như một cây: sai ở node cha thì host thường không đi tiếp được tới node con.

1. Cấu trúc phân cấp

Device Descriptor (1)
└── Configuration Descriptor (1..n)
    └── Interface Descriptor (1..n)
        ├── Class-specific Descriptor (HID, CDC...)
        └── Endpoint Descriptor (1..n)

Device Descriptor mô tả device ở mức cao nhất: USB version, VID, PID, class, số configuration.

Configuration Descriptor mô tả một chế độ hoạt động của device. Phần lớn device chỉ có 1 configuration. Configuration Descriptor chứa wTotalLength — tổng số byte của toàn bộ configuration block bao gồm tất cả Interface và Endpoint Descriptor bên trong.

Interface Descriptor mô tả một “chức năng” trong configuration. Composite device (ví dụ CDC + HID) có 2 interface. Mỗi interface có class, subclass và protocol riêng.

Endpoint Descriptor mô tả một kênh data của interface: địa chỉ endpoint, loại transfer, kích thước packet tối đa, polling interval.

2. Hai byte đầu của mọi descriptor

Mọi USB descriptor đều bắt đầu bằng hai byte:

Offset 0 : bLength         - tổng số byte của descriptor này
Offset 1 : bDescriptorType - loại descriptor
bDescriptorType:
  0x01 = Device
  0x02 = Configuration
  0x03 = String
  0x04 = Interface
  0x05 = Endpoint
  0x21 = HID
  0x22 = HID Report Descriptor

Host dùng bLength để biết bao nhiêu byte cần đọc cho descriptor này, rồi bDescriptorType để biết parse như thế nào. Nếu sai một trong hai, host không thể parse đúng phần còn lại.

3. wTotalLength — điểm dễ gây lỗi nhất

Trong các field của Configuration Descriptor, wTotalLength là field nhỏ nhưng gây lỗi nhiều hơn vẻ ngoài của nó.

wTotalLength là tổng số byte của toàn bộ configuration block, bao gồm chính Configuration Descriptor và tất cả Interface, Endpoint, class-specific Descriptor bên trong.

wTotalLength = sizeof(Configuration Descriptor)   9 byte
             + sizeof(Interface Descriptor)        9 byte
             + sizeof(HID Descriptor)              9 byte
             + sizeof(Endpoint Descriptor)         7 byte
             ─────────────────────────────────────────────
                                                  34 byte

4. Host đọc descriptor theo thứ tự nào?

Thứ tự chính xác có thể khác nhau tùy host, nhưng flow phổ biến là:

1. GET_DESCRIPTOR(Device, 8 byte)       biết bMaxPacketSize0
2. GET_DESCRIPTOR(Device, đầy đủ)       VID, PID, bNumConfigurations
3. GET_DESCRIPTOR(Configuration, 9 byte) biết wTotalLength
4. GET_DESCRIPTOR(Configuration, full)  toàn bộ config block
5. GET_DESCRIPTOR(String, 0)            language ID list
6. GET_DESCRIPTOR(String, n)            Manufacturer, Product, Serial...

Host thường đọc 9 byte đầu của Configuration Descriptor trước để biết wTotalLength, sau đó mới request lại toàn bộ configuration block. Sau bước 4, host đã có đủ thông tin để load driver.

5. Class-specific descriptor

Một số USB class thêm descriptor riêng giữa Interface và Endpoint Descriptor. HID thêm HID Descriptor (9 byte) chứa thông tin về HID Report Descriptor:

Interface Descriptor  (bInterfaceClass = 0x03 = HID)
├── HID Descriptor    (bDescriptorType = 0x21)
└── Endpoint Descriptor IN Interrupt

Với HID, host còn đọc thêm HID Report Descriptor bằng GET_DESCRIPTOR với descriptor type 0x22 trên interface HID. Đây là descriptor do HID class định nghĩa, không nằm trực tiếp trong configuration block.

6. Lỗi thường gặp

Lỗi descriptor phổ biến và hậu quả
Mục Giá trị Ghi chú
wTotalLength sai Host đọc thiếu/thừa, endpoint không được nhận diện
bLength sai trong một descriptor Host parse sai toàn bộ phần còn lại
bNumEndpoints không khớp số Endpoint Descriptor Một số endpoint bị bỏ qua
Endpoint address/direction trùng nhau Transfer fail không rõ lý do
HID Descriptor thiếu hoặc đặt sai vị trí Host không load HID driver

Khi debug descriptor, nên nhớ rằng field này nhỏ nhưng nó quyết định host sẽ hiểu toàn bộ phần còn lại như thế nào.

Bài liên quan

Nguồn tham khảo public

Ghi chú từ project thực tế

  • CubeMX sinh mouse descriptor mặc định dù chọn HID Keyboard. Luôn verify bằng USBView sau enumerate, không phải bằng đọc source.
  • wTotalLength phải tính lại thủ công khi thêm interface. static_assert so sánh với sizeof(array) giúp bắt lỗi lúc build.
  • Composite cần bDeviceClass=0xEF/0x02/0x01 khi dùng IAD. Thiếu triple này, USBView báo *!*ERROR: device class should be Multi-interface Function 0xEF.

Xem chi tiết: bài 3 USB Descriptor

Bài viết này hữu ích với bạn?

Chia sẻ, góp ý, hoặc ủng hộ nếu bạn thấy nội dung này có giá trị.

Góp ý

Nội dung liên quan

Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.

Biến note thành bài viết hoàn chỉnh

Notes là nơi ghi nhanh khái niệm.