- Phát triển trình điều khiển USB thường được xem là công việc ở cấp độ kernel, nhưng trên thực tế cũng có thể triển khai trong không gian người dùng với độ khó tương tự lập trình socket
- Với libusb, bạn có thể thực hiện liệt kê thiết bị, truyền điều khiển và gửi/nhận dữ liệu mà không cần viết mã kernel
- Giao tiếp USB gồm bốn kiểu truyền Control, Bulk, Interrupt, Isochronous cùng với hướng IN/OUT, và mỗi endpoint hoạt động như một kênh một chiều
- Lấy giao thức Fastboot của thiết bị Android làm ví dụ, bài viết minh họa bằng mã cách gửi lệnh và nhận phản hồi qua endpoint Bulk
- Ngay cả trong không gian người dùng, bạn vẫn có thể triển khai một trình điều khiển USB hoàn chỉnh, và mọi giao thức USB đều chia sẻ cùng một cấu trúc cơ bản
Giới thiệu
- Trình điều khiển cho thiết bị USB thường khiến người ta cảm thấy khó vì nghĩ rằng phải xử lý mã kernel, nhưng thực tế chỉ có mức độ phức tạp tương đương ứng dụng dùng socket
- Ngay cả các lập trình viên không có nhiều kinh nghiệm phần cứng cũng có thể học cách làm việc với USB trong không gian người dùng
- Dù đã có tài liệu nói về chi tiết hoạt động của USB, người mới bắt đầu vẫn khó tiếp cận
- Việc sử dụng USB không đòi hỏi kiến thức ở mức hệ thống nhúng, mà có thể tiếp cận giống như socket mạng
Thiết bị USB
- Ví dụ sử dụng điện thoại thông minh Android ở chế độ bootloader
- Dễ kiếm, giao thức đơn giản, và phù hợp để thử nghiệm vì hệ điều hành không có trình điều khiển mặc định
- Cách vào chế độ bootloader khác nhau tùy thiết bị, nhưng thường có thể thực hiện bằng tổ hợp nút nguồn và nút âm lượng
Liệt kê thiết bị thủ công
- Enumeration là quá trình host yêu cầu thông tin thiết bị để nhận diện nó, và được thực hiện tự động khi thiết bị được kết nối
- Với thiết bị chuẩn, trình điều khiển được tự động nạp dựa trên USB class, còn thiết bị dành riêng cho nhà sản xuất dùng
VID (Vendor ID) và PID (Product ID)
- Trên Linux, có thể kiểm tra thông tin thiết bị bằng lệnh
lsusb
- Ví dụ:
ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)
18d1 là VID của Google, 4ee0 là PID của bootloader Nexus/Pixel
- Có thể dùng lệnh
lsusb -t để kiểm tra class và trạng thái driver
- Nếu hiển thị
Class=Vendor Specific Class, Driver=[none] thì hệ điều hành không nạp driver
- Trên Windows, có thể xem cùng thông tin này bằng Device Manager hoặc USB Device Tree Viewer
Liệt kê thiết bị với libusb
- Thư viện libusb cho phép giao tiếp với thiết bị USB trong không gian người dùng mà không cần viết mã kernel
- Có thể dùng
libusb_hotplug_register_callback() để thiết lập callback chạy khi thiết bị với cặp VID:PID cụ thể được kết nối
- Khi chạy chương trình rồi cắm thiết bị vào, sẽ in ra thông báo
"Device plugged in!"
- Trên Linux, cách này hoạt động mặc định; nếu cần, có thể tách driver kernel bằng
libusb_detach_kernel_driver()
- Trên Windows, cần driver
Winusb.sys; nếu chưa có, có thể thay thủ công bằng công cụ Zadig
Giao tiếp với thiết bị
- Lần giao tiếp đầu tiên với thiết bị USB được thực hiện qua endpoint Control (địa chỉ 0x00)
- Dùng
libusb_control_transfer() để gửi yêu cầu chuẩn (GET_STATUS) và đọc trạng thái thiết bị
- Ví dụ phản hồi:
01 00 → byte đầu là Self-Powered, byte thứ hai là không hỗ trợ Remote Wakeup
- Sau đó có thể lấy descriptor của thiết bị bằng yêu cầu GET_DESCRIPTOR
- Dữ liệu trả về bao gồm các thông tin như
idVendor, idProduct, bDeviceClass
- Có thể dùng lệnh
lsusb -v để xem chi tiết mọi descriptor (thiết bị, cấu hình, interface, endpoint, v.v.)
- Ví dụ: interface
Android Fastboot có các endpoint Bulk IN (0x81) và Bulk OUT (0x02)
Endpoint
- Endpoint là khái niệm tương tự cổng mạng, là kênh để thiết bị gửi và nhận dữ liệu
- Mỗi descriptor định nghĩa loại và hướng của từng endpoint
-
Kiểu truyền Control
- Mọi thiết bị đều có một endpoint kiểu này và địa chỉ luôn là
0x00
- Dùng cho thiết lập ban đầu và yêu cầu thông tin thiết bị
- Không thuộc về interface nào mà tồn tại như một phần của chính thiết bị
-
Kiểu truyền Bulk
- Dùng cho truyền dữ liệu lớn không thời gian thực
- Ví dụ: Mass Storage, CDC-ACM (serial), RNDIS (Ethernet)
- Băng thông cao nhưng ưu tiên thấp
-
Kiểu truyền Interrupt
- Dùng cho truyền lượng dữ liệu nhỏ với độ trễ thấp
- Thường dùng để polling nhanh thao tác nút bấm trên bàn phím, chuột, v.v.
- Đây không phải ngắt phần cứng thực sự; host sẽ yêu cầu theo chu kỳ
-
Kiểu truyền Isochronous
- Dùng cho dữ liệu lớn nhạy cảm về thời gian như truyền phát âm thanh, video
- Nếu phát sinh độ trễ, chất lượng sẽ giảm rõ rệt ngay lập tức
- Trong libusb, kiểu này được xử lý theo phương thức bất đồng bộ
-
Hướng IN / OUT
- USB là kiến trúc lấy host làm trung tâm, nên thiết bị không thể tự gửi dữ liệu trước khi nhận yêu cầu
IN: hướng host nhận dữ liệu
OUT: hướng host gửi dữ liệu
- Nếu bit cao nhất (MSB) của địa chỉ endpoint là
1 thì là IN, 0 thì là OUT
- Có thể dùng tối đa 127 endpoint do người dùng định nghĩa (
0x00 dành riêng cho Control)
- Endpoint là một chiều, nên thường được ghép thành cặp IN/OUT như ở interface Fastboot
Giao thức Fastboot
- Fastboot là giao thức giao tiếp với bootloader Android, hoạt động theo cấu trúc gửi chuỗi lệnh rồi nhận mã trạng thái 4 byte và dữ liệu
- Ví dụ:
Host: "getvar:version" → Client: "OKAY0.4"
Host: "getvar:nonexistant" → Client: "OKAY"
- Ví dụ mã gửi lệnh Fastboot bằng libusb
Kết luận
- Có thể triển khai một trình điều khiển USB hoàn chỉnh trong không gian người dùng mà không cần viết mã kernel
- Mọi trình điều khiển USB đều tuân theo cùng một nguyên lý cơ bản, chỉ khác nhau ở giao thức
- Ngay cả các giao thức phức tạp như MTP cũng có cùng cấu trúc nền tảng và có thể tiếp cận bằng khái niệm tương tự giao tiếp socket
1 bình luận
Ý kiến trên Hacker News
Đúng là thời điểm hoàn hảo. Tôi sắp nhận một chiếc MOTU MIDI Express XT từ cửa hàng Guitar Center gần nhà
Đây là đồ cũ nên phải chờ vì theo luật họ phải giữ lại trong một khoảng thời gian nhất định. Vấn đề là thiết bị này không dùng MIDI-over-USB tiêu chuẩn mà dùng giao thức độc quyền, nên trên các hệ thống của tôi như Linux, OpenBSD hay Haiku thì không thể dùng trực tiếp qua USB
Trước mắt tôi chỉ cần định tuyến giữa mô-đun synth và controller nên vẫn ổn, nhưng sẽ rất tốt nếu làm cho nó hoạt động cả phía PC
Có một driver Linux hiện có, nhưng độ ổn định còn chưa rõ và cũng không chắc có hỗ trợ XT hay không. Nghe nói vấn đề kernel panic đã được xử lý nhưng vẫn còn issue
Vì vậy tôi định tự viết một driver không gian người dùng dựa trên LibUSB. Nếu có thể expose các cổng MIDI và thêm công cụ định tuyến thì sẽ khá hữu ích
Nếu muốn thử làm kiểu này bằng Go, tôi đã tạo thư viện go-usb cho phép truy cập USB mà không cần cgo
Tôi cũng đã phát triển go-uvc để xử lý thiết bị UVC bằng nó
Gần đây tôi cũng đang triển khai hệ thống usbip trên Macbook M3 theo cách khá tương tự
Tuy nhiên macOS mới có một số hạn chế. Với các thiết bị USB mà hệ thống đã nhận diện, bạn không thể xây dựng driver không gian người dùng dựa trên libusb trừ khi tự tắt các tính năng bảo mật
Cách tiếp cận này rốt cuộc khiến driver USB cũng đóng vai trò như mã ứng dụng. Tức là nó gần với thư viện + chương trình hơn là driver
Ví dụ, tôi tò mò không biết sẽ phải làm thế nào nếu muốn nối một thiết bị USB-Ethernet thành adapter mạng của OS
Giá như tôi đọc được bài này vài năm trước thì lúc reverse engineer các chức năng của laptop đã dễ hơn rất nhiều. Đặc biệt, chương trình điều khiển LED bàn phím vẫn là một trong những dự án tôi thích nhất đến giờ
Đây thật sự là một bài nhập môn rất hữu ích. Làm việc với API phần cứng mức thấp vừa khó vừa đáng giá. Nhờ các lớp trừu tượng của OS hiện đại mà mọi thứ đã dễ hơn, nhưng hiểu được phần bên dưới vẫn rất quan trọng
Đoạn mã C++ trông hơi kỳ. Tôi chưa từng thấy bàn phím nào có thể gõ trực tiếp ký tự mũi tên
->. Đây là cú pháp trailing return type của C++ hiện đại"->". Font chỉ render nó thành mũi tên thôiTôi từng thắc mắc liệu thiết bị USB có hỗ trợ DMA không. Nó chỉ có thể thực hiện thông qua host hay thiết bị cũng có thể truy cập bộ nhớ trực tiếp?
Trước đây tôi từng muốn làm một thiết bị USB đơn giản, nhưng gần như không có thông tin về cách viết descriptor. Phần lớn chỉ là kiểu “hãy tìm một thiết bị tương tự rồi copy và sửa thử”. Điều đó khiến tôi tự hỏi USB có thực sự là một chuẩn tốt hay không
Nếu ai đó yêu cầu tôi “tự viết driver thiết bị USB”, tôi sẽ trả lại thiết bị đó và trước tiên kiểm tra xem có thể xử lý nó như cổng COM ảo hay không