홈으로 돌아가기

libusb를 사용한 USB 드라이버 작성: 개발자를 위한 가이드

기사가 libusb를 통해 커널 코드 작업 없이 USB 드라이버 생성을 설명합니다. Android fastboot 모드 예제, VID/PID 분석, Control Endpoint를 통한 요청 및 디스크립터 디코딩을 고려합니다. 중/시니어 개발자 지향 자료.

5분 만에 USB 드라이버: 이론에서 작동 코드까지
Advertisement 728x90

고통 없는 USB 드라이버: libusb로 이론에서 코드까지

USB 장치용 드라이버를 작성하는 일은 저수준 코드의 복잡성 때문에 개발자들을 종종 위축시킵니다. 하지만 libusb와 같은 적절한 도구키트를 사용하면 소켓을 이용한 네트워크 프로그래밍만큼 간단해집니다. 이 글에서는 Android 장치의 bootloader 모드를 예로 들어 USB 장치용 드라이버를 만드는 과정을 안내하겠습니다.

USB 기초: 개발자가 알아야 할 것

USB 사양은 VID(Vendor ID)와 PID(Product ID)를 통해 장치를 식별하는 표준 메커니즘을 정의합니다. 이러한 식별자는 제조사(VID는 USB-IF에서, PID는 제조사 자체에서 부여)에 할당됩니다. 장치가 연결되면 호스트는 디스크립터를 요청하는데, 이는 펌웨어에 있는 바이너리 구조로 장치 클래스, 기능, 필요한 드라이버 정보가 포함되어 있습니다. 핵심: 대부분의 작업은 Winusb.sys(Windows)나 usbfs(Linux) 같은 표준 드라이버를 통해 사용자 공간에서 처리할 수 있으며, 커널 코드는 필요 없습니다.

중요 용어:

Google AdInline article slot
  • Endpoint: 데이터 전송을 위한 논리 채널(Control, Bulk, Interrupt, Isochronous).
  • Device Descriptor: VID/PID와 기본 매개변수를 포함한 루트 구조.
  • Device Class: 표준화된 기능(HID, Mass Storage)이지만, 많은 장치가 Vendor Specific Class를 사용합니다.

장치 준비: 선택과 설정

bootloader(fastboot) 모드의 Android 장치를 예로 사용하겠습니다. 이 선택 이유:

  • 접근성: 대부분의 스마트폰이 fastboot로 전환 지원.
  • 간단한 프로토콜: Fastboot 문서가 공개적이고 최소주의적.
  • 사전 설치 드라이버 없음: OS가 상호작용을 가로채지 않음.

Bootloader 모드로 전환하려면 보통 전원 켜는 동안 버튼 조합을 길게 누릅니다(예: 볼륨 업 + 전원). Linux에서 장치 식별은 lsusb로:

$ lsusb
Bus 008 Device 014: ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)

여기서 18d1은 Google의 VID, 4ee0은 fastboot의 PID입니다. Windows에서는 USB Device Tree Viewer로 유사 정보 확인. 핵심: OS가 드라이버를 로드하면(예: 장치 관리자에 ⚠️ 표시), Zadig으로 Winusb.sys로 교체해야 합니다.

Google AdInline article slot

장치 열거: 수동 분석에서 프로그램 구현까지

연결 시 열거 과정이 자동 시작됩니다. OS는 VID/PID와 장치 클래스를 분석해 드라이버를 선택합니다. 사용자 공간에서는 libusb로 구현합니다. 연결 핸들러 등록 코드:

#include <print>
#include <libusb-1.0/libusb.h>

auto hotplug_callback(
    libusb_context *ctx,
    libusb_device *device,
    libusb_hotplug_event event,
    void *user_data
) -> int {
    std::println("Device plugged in!");
    return 0;
}

auto main() -> int {
    libusb_context *context = nullptr;
    libusb_init(&context);

    libusb_hotplug_callback_handle handle;
    libusb_hotplug_register_callback(
        context,
        LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
        LIBUSB_HOTPLUG_ENUMERATE,
        0x18d1, 0x4ee0,
        LIBUSB_HOTPLUG_MATCH_ANY,
        hotplug_callback, nullptr,
        &handle
    );

    while (true) {
        if (libusb_handle_events(context) < 0) break;
    }

    libusb_hotplug_deregister_callback(context, handle);
    libusb_exit(context);
}

이 코드는 libusb를 초기화하고 VID=0x18d1, PID=0x4ee0 장치의 콜백을 등록한 후 연결을 기다립니다. 성공 시 "Device plugged in!"을 출력합니다. Windows에서는 커널 드라이버 분리 필요할 수 있습니다:

libusb_detach_kernel_driver(handle, 0);

Control Endpoint를 통한 장치 통신

기본 상호작용에는 Control Endpoint(ID 0x00)를 사용하세요. 이는 서비스 요청을 위한 표준 채널입니다. GET_STATUS 요청 구현:

Google AdInline article slot
libusb_device_handle *handle = nullptr;
libusb_open(device, &handle);

std::vector<std::uint8_t> data(0xFF);
const auto result = libusb_control_transfer(
    handle,
    LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_STANDARD,
    LIBUSB_REQUEST_GET_STATUS,
    0x00, 0x00,
    data.data(), data.size(),
    1000
);

if (result >= 0) print_bytes(std::span(data).subspan(0, result));
libusb_close(handle);

성공 응답(예: 01 00)은 사양에 따라 디코딩: 첫 번째 바이트—전원 공급(1 = 배터리), 두 번째—원격 웨이크업 지원(0 = 미지원). Control Endpoint는 GET_DESCRIPTOR, SET_CONFIGURATION 같은 다른 표준 요청도 가능하며, 디스크립터 가져오기에 필수입니다.

디스크립터 요청: 장치 이해의 열쇠

디스크립터는 USB 장치 상호작용의 기반입니다. GET_DESCRIPTOR로 device descriptor 요청:

const auto result = libusb_control_transfer(
    handle,
    LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_STANDARD,
    LIBUSB_REQUEST_GET_DESCRIPTOR,
    (LIBUSB_DT_DEVICE << 8), // Tip deskriptora
    0x00,
    data.data(), data.size(),
    1000
);

수신 데이터는 libusb_device_descriptor 구조와 일치하며, 다음 필드 포함:

  • bcdUSB: USB 사양 버전
  • bMaxPacketSize0: Control Endpoint 최대 패킷 크기
  • idVendor/idProduct: VID와 PID
  • bNumConfigurations: 구성 수

디스크립터 분석으로 지원 인터페이스와 엔드포인트 확인. 구성 디스크립터는 요청 유형을 LIBUSB_DT_CONFIG로 변경하고 wIndex에 구성 인덱스 지정.

핵심 포인트

  • 사용자 공간 작업: 커널 코드 대신 libusb 사용—디버깅 쉽고 안전.
  • VID/PID를 주요 식별자로: 드라이버 바인딩에 의존; 장치 클래스는 종종 유용하지 않음(Vendor Specific Class).
  • Control Endpoint를 기본 도구로: 다른 엔드포인트 설정 전에 디스크립터 가져오고 장치 제어.
  • 디스크립터를 진실의 원천으로: USB 사양에 구조 정의; 디코딩으로 장치 기능 전체 파악.

— Editorial Team

Advertisement 728x90

다음 읽기