---
title: "소프트웨어 개발자를 위한 USB 입문 — 유저스페이스 드라이버 직접 만들기"
published: 2026-04-08T19:23:34.000Z
canonical: https://jeff.news/article/1638
---
# 소프트웨어 개발자를 위한 USB 입문 — 유저스페이스 드라이버 직접 만들기

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

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

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

> [!TIP]
> 커널 코드 한 줄 안 짜고도 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 포함), 설정/인터페이스/엔드포인트 디스크립터 수신

```mermaid
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로 작성 가능하며, 소켓 프로그래밍 수준의 난이도임
- 컨트롤 엔드포인트(0x00)는 모든 USB 디바이스에 존재하며 GET_DESCRIPTOR로 디바이스 정보를 열거함
- 엔드포인트 유형은 Control, Bulk, Interrupt, Isochronous 4가지이며 각각 용도와 우선순위가 다름
- USB 엔드포인트는 네트워크 포트, 열거는 포트 스캐닝과 유사한 개념임
- Fastboot 프로토콜은 문자열 명령 전송 → 4글자 상태코드+데이터 응답의 단순한 구조임

## 인사이트

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