본문으로 건너뛰기
피드

소프트웨어 개발자를 위한 USB 입문 — 유저스페이스 드라이버 직접 만들기

backend 약 8분
vote
0
댓글
북마크

커널 코드 없이 libusb를 사용해 유저스페이스에서 USB 드라이버를 작성하는 방법을 안드로이드 Fastboot 프로토콜을 예시로 설명하는 튜토리얼. USB 엔드포인트 유형, 열거(enumeration) 과정, 컨트롤/벌크 전송의 동작 원리를 실습 중심으로 다룸.

  • 1

    USB 드라이버는 커널이 아닌 유저스페이스에서 libusb로 작성 가능하며, 소켓 프로그래밍 수준의 난이도임

  • 2

    컨트롤 엔드포인트(0x00)는 모든 USB 디바이스에 존재하며 GET_DESCRIPTOR로 디바이스 정보를 열거함

  • 3

    엔드포인트 유형은 Control, Bulk, Interrupt, Isochronous 4가지이며 각각 용도와 우선순위가 다름

  • 4

    USB 엔드포인트는 네트워크 포트, 열거는 포트 스캐닝과 유사한 개념임

  • 5

    Fastboot 프로토콜은 문자열 명령 전송 → 4글자 상태코드+데이터 응답의 단순한 구조임

USB 드라이버, 커널 없이도 만들 수 있음

  • USB 드라이버라고 하면 커널 코드를 떠올리지만, 실제로는 유저스페이스에서 소켓 프로그래밍 수준으로 작성 가능함
  • 이 글은 안드로이드 폰의 부트로더 모드를 대상 디바이스로 사용해 처음부터 USB 드라이버를 만드는 과정을 보여줌
    • 부트로더 모드를 선택한 이유: 쉽게 구할 수 있고, Fastboot 프로토콜이 단순하며, 기존 드라이버가 간섭하지 않음

💡

> 커널 코드 한 줄 안 짜고도 USB 드라이버를 만들 수 있음. USB 통신은 소켓 프로그래밍과 크게 다르지 않음.

디바이스 수동 열거(Enumeration)

  • lsusb로 VID:PID 확인 가능함
    • VID 18d1 = Google, PID 4ee0 = Nexus/Pixel Bootloader
    • VID는 USB-IF가 기업에 할당하고, PID는 기업이 제품별로 부여함
  • lsusb -t로 디바이스 클래스와 현재 드라이버 상태 확인
    • Class=Vendor Specific → 표준 클래스(HID, Mass Storage 등)가 아닌 제조사 전용 프로토콜
    • Driver=[none] → OS가 드라이버를 로드하지 않은 상태

libusb로 유저스페이스 드라이버 작성

  • libusb는 커널 드라이버 없이 USB 디바이스와 직접 통신할 수 있는 라이브러리임
    • 범용 드라이버를 로드한 뒤, 유저스페이스 앱이 디바이스를 점유(claim)해서 직접 통신하는 방식
  • VID:PID 조합으로 핫플러그 이벤트 핸들러를 등록하면 디바이스 연결을 감지할 수 있음

컨트롤 엔드포인트와 디바이스 정보 요청

  • 컨트롤 엔드포인트(0x00)는 모든 USB 디바이스에 반드시 존재함
    • 닭과 달걀 문제를 해결함: 엔드포인트를 알려면 통신해야 하고, 통신하려면 엔드포인트를 알아야 하는 문제
  • GET_STATUS 요청 → 셀프 파워 여부, 리모트 웨이크업 지원 여부 확인
  • GET_DESCRIPTOR 요청 → 디바이스 디스크립터(VID/PID 포함), 설정/인터페이스/엔드포인트 디스크립터 수신
sequenceDiagram
    participant Host
    participant Device

    Host->>Device: 디바이스 연결 (플러그인)
    Host->>Device: GET_STATUS (Control EP 0x00)
    Device-->>Host: Self-Powered=1, Remote Wakeup=0
    Host->>Device: GET_DESCRIPTOR (Device)
    Device-->>Host: VID=18d1, PID=4ee0, ...
    Host->>Device: GET_DESCRIPTOR (Configuration)
    Device-->>Host: Interface + Endpoint 목록
    Host->>Device: Bulk OUT — "getvar:version"
    Device-->>Host: Bulk IN — "OKAY0.4"

엔드포인트 유형 정리

  • Control: 디바이스당 1개, 항상 주소 0x00에 고정. 초기 설정과 정보 요청에 사용됨
  • Bulk: 대용량 데이터 전송용. 높은 대역폭이지만 낮은 우선순위
    • Mass Storage, CDC-ACM(시리얼 포트), RNDIS(이더넷) 등에서 사용
    • Interrupt/Isochronous 전송이 있으면 대역폭이 양보됨
  • Interrupt: 소량 데이터를 저지연으로 전송. 실제 인터럽트가 아니라 호스트가 빠르게 폴링하는 방식임
    • HID 키보드/마우스가 초당 1000회 이상 폴링함
  • Isochronous: 타이밍이 중요한 스트리밍용. 오디오/비디오 클래스에서 주로 사용됨

IN/OUT 방향과 엔드포인트 주소

  • USB는 완전한 마스터-슬레이브 구조임. 호스트만 요청을 보내고 디바이스는 요청 없이 절대 먼저 말하지 않음
  • 엔드포인트 주소의 MSB(최상위 비트)가 방향을 결정함
    • MSB=1 → IN(호스트가 데이터 수신), MSB=0 → OUT(호스트가 데이터 송신)
  • 커스텀 엔드포인트는 최대 127개 가능 (7비트 주소 - 컨트롤 엔드포인트 1개 제외)
  • 각 엔드포인트는 단방향 전용이라서 Fastboot 인터페이스도 Bulk IN + Bulk OUT 두 개를 가짐

Fastboot 프로토콜 구현

  • 프로토콜이 매우 단순함: 호스트가 문자열 명령을 보내면, 디바이스가 4글자 상태 코드 + 데이터로 응답
  • "getvar:version" 전송 → "OKAY0.4" 수신
    • 앞 4바이트 OKAY = 성공 상태
    • 나머지 0.4 = Fastboot 프로토콜 버전

기술 맥락

전통적으로 USB 드라이버는 커널 모듈로 작성해야 한다는 인식이 강했어요. 커널 개발은 디버깅이 어렵고 실수 한 번이면 시스템 전체가 멈출 수 있어서 진입 장벽이 높았거든요. 하지만 libusb 같은 유저스페이스 라이브러리가 등장하면서 이 상황이 크게 바뀌었어요.

libusb는 리눅스의 usbfs, macOS의 IOKit, Windows의 WinUSB 같은 OS별 저수준 인터페이스를 추상화해서 크로스 플랫폼 USB 통신을 가능하게 해줘요. 커널 드라이버 대신 범용 드라이버가 디바이스를 잡아두고, 유저스페이스 앱이 직접 엔드포인트를 열어서 데이터를 주고받는 구조거든요. 이 방식이면 일반 애플리케이션 개발자도 USB 프로토콜을 다룰 수 있어요.

이 글에서 강조하는 핵심 비유도 인상적이에요. USB 엔드포인트는 네트워크 포트와 같고, 열거(enumeration)는 포트 스캐닝과 비슷하지만 표준화되어 있다는 거예요. 실제로 Bulk 전송은 TCP 소켓으로 데이터를 보내는 것과 개념적으로 거의 동일해요.

Fastboot처럼 단순한 프로토콜부터 시작하면 USB의 전체 구조를 파악하기 훨씬 수월해요. 다만 실무에서는 MTP처럼 복잡한 프로토콜도 있어서, 프로토콜 자체의 난이도는 천차만별이지만 USB 통신 인프라를 다루는 부분은 동일하다는 점이 이 글의 핵심 메시지예요.

USB 통신은 소켓 프로그래밍과 구조적으로 유사하며, libusb를 쓰면 커널 코드 없이도 완전한 USB 드라이버를 만들 수 있다는 것이 핵심 메시지임.

댓글

댓글

댓글을 불러오는 중...

backend

Go에서 Rust로 옮길 때 진짜로 바뀌는 것들

이 글은 Go 백엔드 서비스를 Rust로 옮길 때 속도보다 컴파일 타임 보장, 런타임 트레이드오프, 개발자 경험이 더 중요하다고 설명한다. nil 패닉, 데이터 레이스, 에러 처리, 제네릭, 비동기 모델, 마이그레이션 전략까지 실무 관점에서 Go와 Rust를 길게 비교한다.

backend

Python 3.15에서 헤드라인은 못 탔지만 꽤 쓸만한 기능들

Python 3.15에는 lazy imports나 Tachyon profiler 같은 큰 기능 말고도 실무에서 바로 체감될 만한 작은 개선들이 들어가. TaskGroup 취소, 컨텍스트 매니저 데코레이터 개선, 스레드 안전 이터레이터처럼 평소 애매하게 불편했던 지점들이 꽤 깔끔해졌어.

backend

심평원, DUR부터 의료영상 심사까지 클라우드로 갈아엎는다

심평원이 정보시스템 클라우드 전환과 함께 병·의원 업무에 직접 닿는 DUR, 의료영상 AI 심사, 요양급여내역 조회 시스템을 고도화한다. 핵심은 설치형 프로그램 중심이던 연계를 웹과 API 기반으로 넓히고, 진료·청구 과정에서 실시간 확인과 자동 판독을 강화하는 쪽이다.

backend

윈도우 에러 코드 7번 ‘ERROR_ARENA_TRASHED’는 어디서 왔을까

ERROR_ARENA_TRASHED는 Win32에서 실제로 쓰이는 현대적 에러라기보다 MS-DOS 시절 메모리 관리 구조에서 넘어온 잔재야. MS-DOS가 메모리 블록 앞의 arena 시그니처를 훑다가 예상한 값이 아니면 ‘arena가 망가졌다’고 보고 이 에러를 냈다는 이야기야.

backend

C/C++ 컴파일러의 느슨한 메모리 동시성 버그를 자동으로 잡는 박사논문

C와 C++ 컴파일러에서 relaxed memory 동시성 버그를 찾는 자동 테스트 프레임워크를 다룬 박사논문이 공개됐어. Téléchat, Atomic-mixer 같은 도구로 소스 수준 동작과 컴파일된 프로그램 동작을 비교하고, LLVM과 GCC 툴체인에서 실제 버그를 찾아낸 내용이 핵심이야.