소프트웨어 개발자를 위한 USB: 사용자 공간 USB 드라이버 작성 입문
3 days ago
6
- USB 드라이버 개발은 커널 수준의 작업으로 여겨지지만, 실제로는 소켓 프로그래밍과 유사한 난이도로 사용자 공간에서도 구현 가능함
-
libusb를 사용하면 커널 코드를 작성하지 않고도 장치 열거, 제어 전송, 데이터 송수신을 모두 수행할 수 있음
- USB 통신은 Control, Bulk, Interrupt, Isochronous 네 가지 전송형과 IN/OUT 방향으로 구성되며, 각 엔드포인트는 단방향 통로로 동작함
- Android 기기의 Fastboot 프로토콜을 예시로, Bulk 엔드포인트를 통해 명령과 응답을 주고받는 과정을 코드로 시연함
- 사용자 공간에서도 완전한 USB 드라이버를 구현할 수 있으며, 모든 USB 프로토콜은 동일한 기본 구조를 공유함
소개
- USB 장치용 드라이버는 커널 코드를 다뤄야 한다는 인식 때문에 어렵게 느껴지지만, 실제로는 소켓을 사용하는 애플리케이션 수준의 복잡도임
- 하드웨어 경험이 많지 않은 개발자도 사용자 공간에서 USB를 다루는 방법을 익힐 수 있음
- USB의 세부 동작을 다루는 자료가 존재하지만 초보자에게는 접근이 어려움
- USB 사용에는 임베디드 시스템 수준의 지식이 필요하지 않으며, 네트워크 소켓처럼 접근 가능함
USB 장치
- 예제로 부트로더 모드의 Android 스마트폰을 사용
- 쉽게 구할 수 있고, 프로토콜이 단순하며, OS에 기본 드라이버가 없어 실험에 적합함
- 부트로더 모드 진입은 기기마다 다르며, 일반적으로 전원 버튼과 볼륨 버튼 조합으로 가능함
장치 수동 열거
-
열거(Enumeration) 는 호스트가 장치 정보를 요청해 자신을 식별하는 과정으로, 장치 연결 시 자동 수행됨
- 표준 장치는 USB 클래스를 기반으로 드라이버를 자동 로드하고, 벤더 전용 장치는 VID(Vendor ID)와 PID(Product ID)를 사용함
- Linux에서는 lsusb 명령으로 장치 정보를 확인 가능
- 예시: ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)
-
18d1은 Google의 VID, 4ee0은 Nexus/Pixel 부트로더의 PID
-
lsusb -t 명령으로 클래스와 드라이버 상태를 확인 가능
-
Class=Vendor Specific Class, Driver=[none]으로 표시되어 OS가 드라이버를 로드하지 않음
- Windows에서는 Device Manager나 USB Device Tree Viewer로 동일한 정보 확인 가능
libusb로 장치 열거
-
libusb 라이브러리를 사용하면 커널 코드를 작성하지 않고 사용자 공간에서 USB 장치와 통신 가능
-
libusb_hotplug_register_callback()으로 특정 VID:PID 조합의 장치가 연결될 때 콜백을 실행하도록 설정
- 프로그램 실행 후 장치 연결 시 "Device plugged in!" 메시지 출력
- Linux에서는 기본적으로 작동하며, 필요 시 libusb_detach_kernel_driver()로 커널 드라이버를 분리 가능
- Windows에서는 Winusb.sys 드라이버가 필요하며, 없을 경우 Zadig 도구로 수동 교체 가능
장치와의 통신
- USB 장치와의 첫 통신은 Control 엔드포인트(주소 0x00) 를 통해 수행
-
libusb_control_transfer()로 표준 요청(GET_STATUS) 을 전송해 장치 상태를 읽음
- 예시 응답: 01 00 → 첫 바이트는 Self-Powered, 두 번째는 Remote Wakeup 미지원
- 이후 GET_DESCRIPTOR 요청으로 장치 디스크립터를 가져올 수 있음
- 반환된 데이터에는 idVendor, idProduct, bDeviceClass 등 장치 정보가 포함됨
-
lsusb -v 명령으로 모든 디스크립터(장치, 구성, 인터페이스, 엔드포인트 등)를 상세히 확인 가능
- 예시: Android Fastboot 인터페이스에 Bulk IN(0x81), Bulk OUT(0x02) 엔드포인트 존재
엔드포인트
- 엔드포인트는 네트워크 포트와 유사한 개념으로, 장치가 데이터를 송수신하는 통로
- 디스크립터에 각 엔드포인트의 종류와 방향이 정의되어 있음
-
Control 전송형
- 모든 장치에 하나 존재하며 주소는 항상 0x00
- 초기 설정 및 장치 정보 요청에 사용
- 인터페이스에 속하지 않고 장치 자체의 일부로 존재
-
Bulk 전송형
-
대용량 비실시간 데이터 전송에 사용
- 예: Mass Storage, CDC-ACM(시리얼), RNDIS(이더넷)
- 대역폭은 높지만 우선순위는 낮음
-
Interrupt 전송형
-
소량의 저지연 데이터 전송에 사용
- 키보드, 마우스 등에서 버튼 입력을 빠르게 폴링
- 실제 하드웨어 인터럽트는 아니며, 호스트가 주기적으로 요청함
-
Isochronous 전송형
-
시간 민감한 대용량 데이터(오디오, 비디오 스트리밍)에 사용
- 지연이 발생하면 품질 저하가 즉시 드러남
- libusb에서는 비동기 방식으로 처리
-
IN / OUT 방향
- USB는 호스트 중심 구조로, 장치는 요청을 받기 전에는 데이터를 전송하지 않음
-
IN: 호스트가 데이터를 받는 방향
-
OUT: 호스트가 데이터를 보내는 방향
- 엔드포인트 주소의 최상위 비트(MSB)가 1이면 IN, 0이면 OUT
- 최대 127개의 사용자 정의 엔드포인트 사용 가능 (0x00은 Control 전용)
- 엔드포인트는 단방향이며, Fastboot 인터페이스처럼 IN/OUT 쌍으로 구성됨
Fastboot 프로토콜
-
Fastboot는 Android 부트로더 통신 프로토콜로, 명령 문자열을 보내고 4바이트 상태 코드와 데이터를 받는 구조
- 예:
-
Host: "getvar:version" → Client: "OKAY0.4"
-
Host: "getvar:nonexistant" → Client: "OKAY"
- libusb를 이용해 Fastboot 명령을 전송하는 코드 예시
- 인터페이스 0을 libusb_claim_interface()로 확보
-
"getvar:version" 명령을 Bulk OUT(0x02) 엔드포인트로 전송
-
Bulk IN(0x81) 엔드포인트로 응답 수신
- 출력 예시:
Request: getvar:version
Response: OKAY0.4
-
OKAY는 성공 상태, 0.4는 Fastboot 버전
마무리
- 커널 코드를 작성하지 않고 사용자 공간에서 완전한 USB 드라이버를 구현 가능
- 모든 USB 드라이버는 동일한 기본 원리를 따르며, 프로토콜만 다름
- 복잡한 프로토콜(MTP 등)도 기본 구조는 동일하며, 소켓 통신과 유사한 개념으로 접근 가능함
-
Homepage
-
개발자
- 소프트웨어 개발자를 위한 USB: 사용자 공간 USB 드라이버 작성 입문