안경잡이개발자

728x90
반응형

  라즈베리파이(Raspberry Pi) Zero W를 이용하면 나만의 커스텀 USB HID 기기를 만들 수 있다. 이는 엄밀히 말하면 USB OTG 포트를 이용하여 라즈베리파이를 일종의 USB 디바이스(device)로 사용하는 것으로, 대용량 저장소(mass storage)부터 시작하여 키보드(keyboard)나 마우스 등의 HID 기기까지 다양한 USB 기능을 제공할 수 있다. 하지만 나만의 커스텀 HID 기기를 만드는 방법에 관해서는 소개하고 있는 글이 적다. 본 포스팅에서는 나만의 커스텀 HID 장치를 만드는 방법을 소개한다.

 

1. 커스텀 HID 기기의 Descriptor 명시 및 구현

 

※ Libcomposite 라이브러리 소개 ※

 

  최신 리눅스 커널(kernel)은 libcomposite 라이브러리를 제공한다. 이 라이브러리는 여러 개의 가젯(gadget)을 한꺼번에 활성화 할 수 있도록 해준다. 여기에서 가젯(gadget) 모드란, 리눅스 시스템을 호스트 컴퓨터의 입장에서 볼 때 USB 장치인 것처럼 보이게 하는 기능을 의미한다. 라즈베리파이 OS에서도 이를 지원하기 때문에 본 포스팅에서도 libcomposite을 이용하여 커스텀 HID 기능을 제공하도록 한다. 이는 현재 가장 정석적인 방법 중 하나이다.

 

※ USB 장치의 Descriptor 정의하기 ※

 

  USB 장치는 다양한 descriptor에 자신의 정보를 담아 호스트(host) PC에게 전송한다. 호스트 PC 입장에서는 자신에게 연결된 USB 장치가 무엇인지 모르기 때문에, 이는 당연한 절차이다.

  ▶ Device Descriptor: USB 장치의 전반적인 정보를 표현한다. 대표적으로 Vendor ID, Product ID를 통해 이 제품이 어떤 제품인지  고유한 ID를 알려주도록 하고, 어떠한 USB 버전을 따르는지 작성한다. 

 

  ▶ Configuration Descriptor: 각각의 configuration이 사용하는 소비 전력과 인터페이스(interface)의 개수를 명시한다. 일반적으로 USB 장치의 configuration은 1개이고, 인터페이스는 1개 혹은 여러 개를 사용한다.

  ▶ Interface Descriptor: USB 장치의 인터페이스(interface)에 대한 정보를 명시한다. 여기에서 하나의 인터페이스는 키보드와 같은 USB의 하나의 기능(function)을 담당한다. 여기에서 엔드포인트(endpoint)의 개수도 표시해야 한다. 예를 들어 HID가 USB 패킷을 받을 수도 있고, 내보낼 수도 있다면 엔드포인트는 IN, OUT 각각을 위해 2개를 사용하는 것이 일반적이다.

  ▶ String Descriptor: 문자열 형태의 추가적인 정보를 명시한다. 제조업체(manufacturer), 상품 이름(product), 시리얼 번호(serial number)가 여기에 포함된다.

 

  필자의 경우 Vendor ID로 0x1D6B, Product ID로 0x0104를 사용하겠다. 이는 각 Linux Foundation, Multifunction Composite Gadget을 의미한다. 사실 엄밀히 말하면 자기만의 Vendor ID와 Product ID를 정의해서 사용해도 되지만, 리눅스 기본 예제를 따라 이처럼 작성하겠다. 또한 USB의 버전은 2.0을 사용하고, 제조업체명은 "Dongbin Na", 상품 이름은 "My USB"로 설정하겠다. 실질적으로 바로 다음과 같이 스크립트를 작성하면 된다.

 

  필자의 경우 /usr/bin/my_usb라는 이름으로 스크립트를 작성했으며 chmod +7 /usr/bin/my_usb 명령어를 이용하여 실행 권한을 부여했다.

 

#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p my_usb
cd my_usb

# 기본적인 USB 클래스 명시
echo 0x1D6B > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB 2.0

# 내가 만들 USB 장치의 기본적인 이름
mkdir -p strings/0x409
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Dongbin Na" > strings/0x409/manufacturer
echo "My USB" > strings/0x409/product

# 하나의 Configuration 정보 작성
mkdir -p configs/c.1/strings/0x409
echo "My USB Config 1" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# 실질적으로 USB 기능(function)들이 들어갈 공간

# UDC (Usb Device Controller)
ls /sys/class/udc > UDC

 

※ HID 환경 설정 ※

 

  앞서 작성한 스크립트에서는 device descriptor와 configuration descriptor가 명시되었다. 이제 우리는 HID 기기를 만들 것이기 때문에 적절한 interface descriptor를 명시할 필요가 있다.

 

  USB 프로토콜에 따르면, 각각의 기능(function)은 하나의 인터페이스(interface)를 이용하도록 구현된다. HID의 경우 bInterfaceClass로 0x03을 사용하기로 약속되어 있다. 또한 bInterfaceSubclass와 bInterfaceProtocol도 명시해야 하는데, 대부분의 커스텀 HID 디바이스는 bInterfaceSubclass와 bInterfaceProtocol를 모두 0x00으로 설정한다. 참고로 키보드는 bInterfaceProtocol를 1로, 마우스는 bInterfaceProtocol를 2로 사용한다.

다시 말해 커스텀 HID의 경우 다음과 같이 설정하면 된다.

  ▶ Interface Class: 0x03
  Interface Subclass: 0x00
  Interface Protocol: 0x00

 

※ HID Report Descriptor 정의하기 ※

 

  또한 커스텀 HID 장치를 만들기 전에 HID report descriptor의 개념에 대하여 알아야 한다. 모든 HID 기기는 자신이 어떤 HID 기기인지에 대한 정보를 descriptor에 기록하여 호스트(host) PC로 보낸다. 호스트 PC 입장에서는 이 HID 기기가 어떠한 기기인지 알기 위해서 이러한 descriptor를 참고할 수 있다.

 

  HID 기기는 usage를 이용해 그 기능을 명시한다. 이때 usage pageusage ID가 사용된다. 예를 들어 usage page로 0x01을 사용하면 일반적인 데스크탑 목적이라는 의미이고, usage ID로 0x06을 사용하면 키보드 목적이라는 의미가 된다. 일반적으로 사용되는 usage 정보를 확인하고 싶다면 아래 링크를 참고하자.

 

  ▶ 참고: docs.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages

 

  다시 말해 이미 존재하는 키보드(keyboard)나 마우스(mouse)로서 동작하기를 원한다면, 이미 정해진 설정에 따라서 개발을 진행하면 되는 것이다. 하지만 커스텀 HID 장치를 만들고자 한다면 이를 직접 정의할 필요가 있다.

 

  필자는 usage page: 0xFF77, usage ID: 0x0009을 사용한다. 참고로 usage page나 usage ID를 범위 밖으로 마음대로 설정해 버리면 "보고서 ID 선언이 최상위 수준 컬렉션 외부에 있습니다."와 같은 메시지가 출력되며 정상적으로 HID 기기로 인식이 안 될 수도 있다. 따라서 usage page나 usage ID를 설정하는 것도 약속을 따라야 한다. HID descriptor를 만드는 것을 도와주는 USB 협회 공식 제공 툴도 있으니 참고하자.

 

/* Define: Custom HID Report Descriptor */
{
    0x06, 0x77, 0xFF,   // USAGE_PAGE (Vendor Defined)
    0x09, 0x00,         // USAGE (Undefined)
    0xA1, 0x01,         // COLLECTION (Application)
    0x09, 0x01,         // USAGE (Vendor Usage 1)
    0x15, 0x00,         // LOGICAL_MINIMUM (0)
    0x26, 0xFF, 0x00,   // LOGICAL_MAXIMUM (255)
    0x85, 0x01,         // REPORT_ID (1)
    0x75, 0x08,         // REPORT_SIZE (8)
    0x95, 0x40,         // REPORT_COUNT (64)
    0x81, 0x02,         // INPUT (Data, Var, Abs): Device to Host
    0x09, 0x02,         // USAGE (Vendor Usage 2)
    0x15, 0x00,         // LOGICAL_MINIMUM (0)
    0x26, 0xFF, 0x00,   // LOGICAL_MAXIMUM (255)
    0x85, 0x02,         // REPORT_ID (2)
    0x75, 0x08,         // REPORT_SIZE (8)
    0x95, 0x40,         // REPORT_COUNT (64)
    0x91, 0x02,         // OUTPUT (Data, Var, Abs): Host to Device
    0xC0                // END_COLLECTION
};

 

  따라서 최종적으로 다음과 같이 스크립트를 작성하면 된다. 현재 예시에서는 report size는 8비트, report count를 64를 사용한다. 결과적으로 패킷 크기(packet size)를 64 바이트로 설정하는 것이다.

 

mkdir -p functions/hid.usb0
echo 0 > functions/hid.usb0/protocol
echo 0 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x06\\x77\\xFF\\x09\\x00\\xA1\\x01\\x09\\x01\\x15\\x00\\x26\\xFF\\x00\\x85\\x01\\x75\\x08\\x95\\x40\\x81\\x02\\x09\\x02\\x15\\x00\\x26\\xFF\\x00\\x85\\x02\\x75\\x08\\x95\\x40\\x91\\x02\\xC0 > functions/hid.usb0/report_desc

ln -s functions/hid.usb0 configs/c.1/

 

  실제로 HID를 이용하는 경우 HID report descriptor가 매우 중요한 역할을 수행한다. HID report descriptor가 잘못 작성되어 있으면 운영체제가 정상적으로 인식하지 못한다. 특히 윈도우(Windows)의 경우 USB 장치가 무엇인지 알 수 없을 때 기본 USB 드라이버를 적용한다. 따라서 위와 같이 작성한 report descriptor가 잘 동작하지 않을 가능성도 있다.

 

  또한 라즈베리파이 측에서 스크립트에 실행 권한이 없어서 스크립트 자체가 실행되지 않는 경우에도 당연히 운영체제가 정상적으로 인식이 불가능 때문에, 이 부분에 대하여 인지할 필요가 있다. 추가로 윈도우(Windows)에서는 정상적으로 인식하지만 Virtual Machine에서는 인식을 못 하는 경우도 있다. 이럴 때는 VM을 재부팅하도록 하자.

 

※ 최종적인 환경 설정 스크립트 ※

 

  최종적으로 다음과 같은 스크립트를 작성하여 라즈베리파이가 이를 실행하도록 만들면 된다.

 

#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p my_usb
cd my_usb

# 기본적인 USB 클래스 명시
echo 0x1D6B > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB 2.0

# 내가 만들 USB 장치의 기본적인 이름
mkdir -p strings/0x409
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Dongbin Na" > strings/0x409/manufacturer
echo "My USB" > strings/0x409/product

# 하나의 Configuration 정보 작성
mkdir -p configs/c.1/strings/0x409
echo "My USB Config 1" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# 실질적으로 USB 기능(function)들이 들어갈 공간
mkdir -p functions/hid.usb0
echo 0 > functions/hid.usb0/protocol
echo 0 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x06\\x77\\xFF\\x09\\x00\\xA1\\x01\\x09\\x01\\x15\\x00\\x26\\xFF\\x00\\x85\\x01\\x75\\x08\\x95\\x40\\x81\\x02\\x09\\x02\\x15\\x00\\x26\\xFF\\x00\\x85\\x02\\x75\\x08\\x95\\x40\\x91\\x02\\xC0 > functions/hid.usb0/report_desc

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/hid.usb0 configs/c.1/

# UDC (Usb Device Controller)
ls /sys/class/udc > UDC

 

  실제로 라즈베리파이가 위 스크립트를 실행하도록 만들고 라즈베리파이를 호스트 PC(리눅스 OS)에 연결해보자. 참고로 기본적인 OS가 윈도우(Windows)인 사람은 VM을 설치하여 사용할 수 있다. 기본적으로 윈도우(Windows) OS에 연결하면 장치 관리자에 다음과 같이 등장한다. 정상적으로 VID: 1D6B, PID: 0104로 기기가 인식되는 것을 알 수 있다.

 

 

  앞서 말했듯이 Oracle VM VirtualBox 환경이라면 다음과 같이 USB 장치를 인식하도록 해주어야 한다.

 

 

  이후에 호스트 PC에서 lsusb 명령어를 이용해 연결된 USB 정보를 확인하면 다음과 같다. 앞서 정의했던 내용 그대로 환경 설정이 되어 있는 것을 확인할 수 있다. 기본적인 Device Descriptor가 모두 정의했던 대로 인식된다.

 

 

  참고로 USB 가젯의 설정 자체를 바꾼 경우라면 UDC를 새롭게 설정하더라도 재부팅을 하지 않으면 환경 설정 내용이 바뀌지 않는 경우가 있다. 따라서 동적으로 가젯 하나만 더 추가하는 정도가 아니라면 재부팅을 수행하자.

 

2. 커스텀 HID 기기 Host 사이드 구현

 

※ 호스트(Host) 프로그램 만들기 ※

 

  리눅스 호스트(host) 상에서 커스텀 HID 기기를 처리하기 위한 프로그램을 간단히 만들어보자. 사실 Teensy 공식 사이트의 코드를 참고하여 미리 프로그램을 작성해 놓았다. 전체 코드는 필자의 깃허브 저장소에서 확인할 수 있다.

 

  코드 중에서 hid_LINUX.c리눅스의 usb.h에 정의된 함수를 이용해 HID 관련 함수를 정의한 코드이다. HID 장치를 찾는 함수, HID 장치로 USB 패킷을 보내는 함수, HID 장치에서 USB 패킷을 받는 함수가 정의되어 있다. 흔히 HID 프로토콜을 따르되 단순히 interrupt transfer를 이용하여 패킷을 주고 받는 형식을 raw HID라고 부른다. 실제 코드를 확인해 보면 usb_interrupt_read() 함수 usb_interrupt_write() 함수를 이용하는 것을 확인할 수 있다.

 

 

  이후에 custom_hid.c 코드에서는 앞서 정의한 함수들을 이용하여 HID 기기와 통신을 진행한다. 앞서 설명했듯이 vendor ID: 0x1D6B, product ID: 0x0104, usage page: 0xFF77, usage ID: 0x0009를 가지는 HID 기기를 찾는 것을 알 수 있다. HID 기기를 찾아서 연 이후에는 매번 HID 기기로부터 64 바이트씩 데이터를 전달 받도록 한다. 데이터를 받은 이후에는 전달 받은 데이터를 출력하고, 다시 64바이트의 데이터를 채워서 전송한다.

 

 

  단순히 라즈베리파이를 연결한 뒤에 custom_hid 프로그램을 실행하면 다음과 같이 HID 기기를 찾아내는 것을 알 수 있다. 참고로 알려지지 않은 HID 기기를 이용하기 위해서는 sudo 권한으로 프로그램을 실행할 필요가 있다.

 

 

3. 커스텀 HID 기기 Device 사이드 구현

 

※ 호스트(Host)로 데이터 전송해보기 ※

 

  HID gadget을 사용할 때는 단순히 HID 장치에 파일 쓰기(file write)을 이용하여 문제를 해결할 수 있다.  echo 명령어에 ne 옵션을 붙여서 (n 옵션: new line 제외, e 옵션: escape 문자 사용) 호스트로 데이터를 보낼 수 있다. 실제로 라즈베리파이에서 다음과 같은 명령어를 실행하면 된다.

 

sudo bash -c 'echo -ne "\x97\x77" > /dev/hidg0'

 

 

  그러면 호스트(host) 쪽에서는 다음과 같이 메시지를 전송 받게 된다.

 

 

  참고로 Raspberry Pi가 호스트(host) 쪽에서 온 데이터를 읽기 위한 목적으로 sudo cat /dev/hidg0을 입력한 이후에 위와 같은 echo 명령어를 다시 실행하면 write error: Resource temporarily unavailable 와 같은 오류가 발생할 수 있다. 필자의 경우 라즈베리파이와 Linux OS 재부팅 이후에 문제가 사라졌다.

 

※ 호스트(Host)로부터 받은 데이터 확인하기 ※

 

  호스트로부터 받은 데이터를 확인하기 위해서는 간단히 /dev/hidg0에 전달된 데이터가 있는지 확인하면 된다.

 

 

※ Device 사이드 프로그램 작성하기 (기본형) ※

 

  간단하게 무한정 호스트로 데이터를 보내고, 호스트에서 도착한 데이터를 받아 출력하도록 해보자.

 

#include <stdio.h>
#include <unistd.h> // sleep()
#include <string.h> // memset()

int main(void) {
    FILE* file;
    char buf[64];

    while (1) {
        // send to host
        for (int i = 0; i < sizeof(buf); i++) {
            buf[i] = 'A' + i;
        }

        printf("\nsend %d bytes:\n", sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf)- 1) printf("\n");
        }
        printf("\n");
        fflush(stdout);

        file = fopen("/dev/hidg0", "w+");
        fwrite(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("send complete.");
        fflush(stdout);

        // receive from host
        file = fopen("/dev/hidg0", "w+");
        fread(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("\nreceive %d bytes:\n", sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf) - 1) printf("\n"); 
        }
        printf("\n"); 
        fflush(stdout);

        printf("receive complete.");
        fflush(stdout);

        sleep(1);
    }
    return 0;
}

 

  실행하면 디바이스에서는 다음과 같이 동작한다. 매번 호스트로 64 바이트의 데이터를 보낸 뒤에 'F'(46)로 가득 채워진 64 바이트의 데이터를 받는 것을 알 수 있다.

 

 

  반대로 호스트 쪽에서는 데이터를 전달 받은 뒤에 매번 'F'(46)로 가득 채운 64 바이트 크기의 문자열을 돌려준다.

 

 

  이러한 과정이 무한정 반복된다. 참고로 파일 쓰기(file write)과 파일 읽기(file read) 사이에 sleep()을 주어 interrupt polling 시간을 컨트롤하려고 했는데, 중간에 sleep()을 주는 경우에는 호스트로부터 정상적으로 패킷을 받아올 수 없었다. 그 이유에 대해서는 추후에 시간이 날 때 다시 찾아 볼 예정이다. 현재 예상되는 바는, 지금 코드상에서는 호스트 쪽에서 데이터를 보내기까지 무한정 대기하는 것으로 보인다.

 

※ HID 패킷 속도(Speed) 측정하기 ※

 

  이번에는 sleep() 호출 없이 무한정 패킷을 주고 받도록 만들어 보았다. 다음의 코드와 같이 차례대로 순번을 증가시키면서 데이터를 전송했는데, 데이터의 유실 없이 모든 패킷이 빠르게 주고 받아 지는 것을 확인할 수 있었다. 다만 패킷을 주고 받는 속도가 1초당 1,000번 정도는 나올 거라고 생각했는데, 실제로는 그보다 느리게 동작했다. 그래도 초당 30번 정도는 패킷을 주고 받는 것으로 보인다. 다시 말해 초당 2KB 정도의 속도는 나온다는 것인데, 대용량 데이터를 주고 받기에는 한계가 있는 것으로 보인다.

 

#include <stdio.h>
#include <unistd.h> // sleep()
#include <string.h> // memset()

int main(void) {
    FILE* file;
    char buf[64];
    char sign = 0;
    int cnt = 0;

    while (1) {
        // send to host
        for (int i = 0; i < sizeof(buf); i++) {
            buf[i] = 'A' + i;
        }
        buf[sizeof(buf) - 1] = sign++;

        printf("\n[%d] send %d bytes:\n", cnt++, sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf) - 1) printf("\n");
        }
        printf("\n");
        fflush(stdout);

        file = fopen("/dev/hidg0", "w");
        fwrite(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("send complete.");
        fflush(stdout);

        // receive from host
        file = fopen("/dev/hidg0", "r");
        fread(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("\nreceive %d bytes:\n", sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf) - 1) printf("\n"); 
        }
        printf("\n"); 
        fflush(stdout);

        printf("receive complete.");
        fflush(stdout);
    }
    return 0;
}

 

  디바이스(device) 쪽의 출력 형태는 다음과 같다.

 

 

※ Device 사이드 프로그램 작성하기: 호스트에서 데이터 받기만 하기 ※

 

  무작정 호스트로부터 데이터를 받기만 할 때는 다음과 같이 작성한다.

 

#include <stdio.h>
#include <unistd.h> // sleep()
#include <string.h> // memset()

#define BUFFER_SIZE 64 // 패킷(버퍼) 사이즈

FILE* file;
char buf[BUFFER_SIZE];

int main(void) {
    while (1) {
        // receive from host
        file = fopen("/dev/hidg0", "w+");
        fread(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("\nreceive %d bytes:\n", sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf) - 1) printf("\n"); 
        }
        printf("\n"); 
        fflush(stdout);

        printf("receive complete.");
        fflush(stdout);
    }
    return 0;
}

 

  만약 호스트가 예시로 임의의 크기의 데이터를 보내는 코드를 실행하게 되면 다음과 같이 데이터를 받게 된다.

 

 

※ 라즈베리파이 부팅 이후에 프로그램 자동 실행 ※

 

  부팅 이후에 HID로 데이터를 주고 받는 프로그램이 자동으로 실행되도록 만드려면 /etc/rc.local을 수정한다. 기본적으로 프로그램은 백그라운드(background)에서 실행되어야 하므로 다음과 같이 & 기호를 붙인다.

 

 

  이어서 재부팅을 하면 다음과 같이 설정했던 프로그램이 자동으로 정상적으로 실행되는 것을 알 수 있다.

 

 

※ Mass Storage와 함께 쓰기 ※

 

  다음과 같이 파일을 저장하기 위한 목적으로 블록 디바이스를 하나 만들 수 있다.

 

mkdir -p /home/pi/images

# 1Gb 이미지 생성
dd if=/dev/zero of=/home/pi/images/usbdisk1.img bs=1048576 count=1024
mkdosfs /home/pi/images/usbdisk1.img

 

  이제 환경 설정 파일을 다음과 같이 수정하여 HID와 Mass Storage Class를 같이 제공하도록 해보자. 호스트(host) PC 입장에서는 읽기만 가능하다고 가정한다.

 

#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p my_usb
cd my_usb

# 기본적인 USB 클래스 명시
echo 0x1D6B > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB 2.0

# 내가 만들 USB 장치의 기본적인 이름
mkdir -p strings/0x409
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Dongbin Na" > strings/0x409/manufacturer
echo "My USB" > strings/0x409/product

# 하나의 Configuration 정보 작성
mkdir -p configs/c.1/strings/0x409
echo "My USB Config 1" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# 실질적으로 USB 기능(function)들이 들어갈 공간
mkdir -p functions/hid.usb0
echo 0 > functions/hid.usb0/protocol
echo 0 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x06\\x77\\xFF\\x09\\x00\\xA1\\x01\\x09\\x01\\x15\\x00\\x26\\xFF\\x00\\x85\\x01\\x75\\x08\\x95\\x40\\x81\\x02\\x09\\x02\\x15\\x00\\x26\\xFF\\x00\\x85\\x02\\x75\\x08\\x95\\x40\\x91\\x02\\xC0 > functions/hid.usb0/report_desc

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/hid.usb0 configs/c.1/

################# Mass Storage 1 #################
# 첫 번째 이미지 지정하기
FILE1=/home/pi/images/usbdisk1.img

# 해당 이미지에 마운트(mount) 진행하기
mkdir -p ${FILE1/img/d}
mount -o loop, -t vfat $FILE1 ${FILE1/img/d} # 라즈베리 파이 입장에서 쓰기 가능

# USB 기능(function) 작성하기 (kernel.org reference 참고하기)
mkdir -p functions/mass_storage.usb0
echo 1 > functions/mass_storage.usb0/stall
echo 0 > functions/mass_storage.usb0/lun.0/cdrom
echo 1 > functions/mass_storage.usb0/lun.0/ro # 호스트 입장에서는 일기 전용(read-only)
echo 0 > functions/mass_storage.usb0/lun.0/nofua
echo $FILE1 > functions/mass_storage.usb0/lun.0/file # 이미지 파일 명시하기

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/mass_storage.usb0 configs/c.1/

# UDC (Usb Device Controller)
ls /sys/class/udc > UDC

 

  이제 재부팅 이후에 성공적으로 HID 기능을 제공하면서, 그와 동시에 Mass Storage Class로의 기능도 제공하게 된다.  다만 한가지 유의할 점은 라즈베리파이가 호스트로부터 HID로 데이터를 주고받아 블록 장치에 쓰기 연산을 수행하는 상황에서, 호스트 입장에서는 Mass Storage 기능을 이미 제공 받고 있기 때문에 블록 장치에 새롭게 기록된 정보가 호스트 입장에서 즉시 보이지는 않는다. 이때 라즈베리파이의 데이터 케이블의 연결을 잠시  끊었다가 다시 연결하면, 비로소 호스트에 다시 Mass Storage 디바이스가 잡혔을 때 해당 파일들이 보이게 된다.

 

참고 자료

 

  1) 리눅스 공식 HID Gadget 튜토리얼: 본 사이트에서는 기본적인 마우스, 키보드 기능을 구현하는 방법을 소개한다. 커스텀 HID를 만들기 전에 간단히 실습해 보기에 좋다.

 

  2) HID Report Descriptor 생성 프로그램: usb.org 공식 웹 사이트에서 제공하는 HID descriptor 도구다.

728x90
반응형