대상 독자: 임베디드 펌웨어 엔지니어, OS 드라이버 통합 엔지니어, OEM 제품 매니저로서, 리딩펜 HID 리포트 디스크립터의 각 바이트가 실제로 무엇을 의미하는지, Usage Page가 왜 일반 데스크톱 페이지와 벤더 정의 페이지로 분할되어 있는지, 그리고 현장에 이미 배포된 230개 이상의 유통사급 드라이버를 손상시키지 않으면서 맵을 확장하는 방법을 이해해야 하는 분들.
HID 리포트 디스크립터는 리딩펜 펌웨어에서 가장 중요한 단 80~200 바이트입니다. 이는 펌웨어가 탑재된 MCU와 향후 연결될 모든 호스트 운영체제(Windows 10/11, macOS 12/13/14, ChromeOS, Android 9~14, iPadOS 15~17, Linux Kernel 5.15+) 간의 계약입니다. 디스크립터에 오류가 발생하면 펜이 잘못된 문자를 입력하거나 OID 탭을 무시하고, 더 심각한 경우 오디오가 잘못된 싱크로 전송될 수 있습니다. 당사가 출하한 47,000대 이상의 제품군에서 디스크립터 회귀는 OCR 엔진을 제외한 다른 모든 펌웨어 서브시스템보다 더 많은 디버깅 시간을 소모시켰습니다.
본 글에서는 TC-057의 표준 디스크립터를 바이트 단위로 살펴보고, 각 필드가 왜 이러한 값으로 설정되었는지 설명하며, ODM 브랜드가 맵을 안전하게 확장할 수 있는 부분과 확장할 수 없는 부분을 제시합니다.
1. HID 계약의 세 가지 계층
HID 리포트 디스크립터는 세 가지 논리적 계층으로 파싱됩니다.
| 계층 | 세분성 | 목적 | 길이 |
|---|---|---|---|
| 항목 | Short(1바이트) 또는 Long(3바이트) | 태그 + 크기 + 데이터 | 가변 |
| 로컬 | 항목별로 정의됨 | Usage, Usage Minimum/Maximum, 지정자(Designator) | 가변 |
| 메인 | 입력/출력/피처 배열을 감쌈 | Collection, End Collection, Input, Output, Feature | 가변 |
파서는 상태 기반(Stateful)입니다. Collection은 새로운 스코프를 시작하며, 그 스코프 내의 Input/Output/Feature 항목들은 하나의 리포트를 구성합니다. 디스크립터는 왼쪽에서 오른쪽으로 순차적으로 처리되며, 모든 바이트는 올바른 형식이어야 합니다. 단 하나의 잘못된 바이트만 있어도 호스트는 전체 디스크립터를 거부합니다.
당사 Q6/Q8 디스크립터는 92바이트이며, F8 디스크립터는 76바이트입니다. T2/Y6(헤드폰 재생용 컨슈머 제어 완전 지원)은 168바이트입니다. 디스크립터 길이는 열거 시점에 HID 디스크립터에 인코딩되며 호스트는 이를 캐싱합니다. 향후 펌웨어 개정을 통해 디스크립터를 변경하는 경우에도 호스트가 캐시된 버전을 계속 사용할 수 있으므로, 펌웨어 업데이트 시에는 항상 USB 버스 리셋을 강제 수행합니다(TC-075 참조).
2. 리딩펜이 사용하는 아이템 태그
HID 사양은 태그 테이블을 정의하며, 리딩펜은 이 중 12개만 사용합니다.
| 태그 | 니모닉 | 역할 | Q6/Q8 사용 여부 |
|---|---|---|---|
| 0x05 | Usage Page | 페이지를 전환 | 예(3회 사용) |
| 0x09 | Usage | 하나의 Usage를 선택합니다 | 예(4회 사용) |
| 0x15 | Logical Minimum | 입력/출력 범위 설정 | 예(3회 사용) |
| 0x25 | Logical Maximum | 입력/출력 범위 설정 | 예(3회 사용) |
| 0x75 | Report Size | 요소당 비트 수 | 예(3회 사용) |
| 0x95 | Report Count | 요소(Element) 수 | 예(3회 사용) |
| 0xA1 | Collection | 스코프 열기 | 예(2회 사용) |
| 0x81 | Input | 입력 리포트 필드를 정의합니다 | 예 (3회) |
| 0x91 | Output | 출력 리포트 필드를 정의합니다 | 예 (1회) |
| 0x19 | Usage Minimum | 범위 시작 | 예 (1회) |
| 0x29 | Usage Maximum | 범위 종료 | 예 (1회) |
| 0xC0 | End Collection | 범위를 닫습니다 | 예 (2회) |
우리는 의도적으로 이 12개 태그의 하위 집합 내에서만 작업합니다. HID 사양은 더 많은 태그를 허용하지만, 태그를 추가할수록 OS별 특이 동작이 발생할 가능성이 커집니다. 예를 들어 Padding (0x5A) 태그는 기술적으로는 합법적이지만, Linux 커널의 hid-core.c는 suspend/resume 중 일부 Padding 시퀀스를 거부합니다.
3. 세 가지 Usage Page
Q6/Q8 디스크립터는 세 가지 Usage Page를 사용합니다:
| Usage Page | 16진수(Hex) | 용도 |
|---|---|---|
| Generic Desktop | 0x01 | 키보드, 마우스, 시스템 컨트롤 |
| Consumer | 0x0C | 볼륨, 재생/일시정지, 트랙 이동 |
| Vendor Defined | 0xFF00 | OID 이벤트 페이로드 (커스텀 브랜드 확장) |
Generic Desktop 페이지는 출고하는 키보드 컬렉션입니다. 1바이트 수정 키(8비트, 1바이트 리포트 크기, 8 리포트 개수)와 1바이트 예약 상수(8비트, 1 리포트 크기, 1 개수)를 선언합니다. 이 둘이 합쳐져 부트 키보드 리포트 헤더를 구성합니다.
Consumer 페이지는 헤드폰 재생용으로 출고하는 볼륨 업 / 볼륨 다운 / 재생-일시정지 컨트롤입니다. 총 3비트입니다.
Vendor Defined 페이지 0xFF00은 OID 이벤트 페이로드입니다: 리포트당 16바이트, Report ID 없음, Output 리포트에 위치하여 필요한 경우 호스트가 펜에 16바이트 컨텍스트를 푸시할 수도 있습니다.
4. Q6/Q8 펌웨어 3.3.1의 바이트 맵
아래는 Q6/Q8 플랫폼의 표준 디스크립터를 바이트 인덱스로 나타낸 것입니다:
| Byte | 16진수(Hex) | 의미 | 이유 |
|---|---|---|---|
| 0 | 0x05 | Usage Page 태그(Global) | 파서에게 페이지 전환을 알림 |
| 1 | 0x01 | Page 0x01 (Generic Desktop) | 키보드 범위를 열기 |
| 2 | 0x09 | Usage 태그(Local) | 하나의 Usage 선택 |
| 3 | 0x06 | 키보드 | 부트 프로토콜에 필수 |
| 4 | 0xA1 | 컬렉션 태그 (Main) | 애플리케이션 컬렉션을 엽니다 |
| 5 | 0x01 | 애플리케이션 | 애플리케이션은 표준 래퍼입니다 |
| 6 | 0x05 | Usage Page 태그 | |
| 7 | 0x07 | 키보드 페이지 | 수정 키는 여기에 있습니다 |
| 8 | 0x19 | Usage Minimum | |
| 9 | 0xE0 | Left Control = 224 | 수정 키 범위 224–231 |
| 10 | 0x29 | Usage Maximum | |
| 11 | 0xE7 | Right GUI = 231 | 수정 키 범위를 닫습니다 |
| 12 | 0x15 | Logical Minimum | |
| 13 | 0x00 | 0 | Boolean false |
| 14 | 0x25 | Logical Maximum | |
| 15 | 0x01 | 1 | Boolean true |
| 16 | 0x75 | Report Size | |
| 17 | 0x01 | 요소당 1비트 | Modifier는 비트필드입니다 |
| 18 | 0x95 | Report Count | |
| 19 | 0x08 | 8개의 요소 | 수정 키당 1비트 |
| 20 | 0x81 | Input 태그(Main) | |
| 21 | 0x02 | 데이터, 변수, 절대값 | 표준 부팅 키보드 수정자 |
| 22 | 0x95 | 리포트 개수 | |
| 23 | 0x01 | 1개 요소 | 예약 바이트 |
| 24 | 0x75 | 리포트 크기 | |
| 25 | 0x08 | 8비트 | 예약된 1바이트 |
| 26 | 0x81 | Input 태그 | |
| 27 | 0x03 | 상수, 변수, 절대값 | OS에서 기대하는 예약 바이트 |
| 28 | 0x05 | Usage Page 태그 | |
| 29 | 0x0C | Consumer 페이지 | 미디어 키용 |
| 30 | 0x09 | Usage | |
| 31 | 0xE9 | 볼륨 업 | |
| 32 | 0x09 | Usage | |
| 33 | 0xEA | 볼륨 다운 | |
| 34 | 0x09 | Usage | |
| 35 | 0xCD | 재생/일시정지 | |
| 36 | 0x15 | 논리 최소값 | |
| 37 | 0x00 | 0 | |
| 38 | 0x25 | 논리 최대값 | |
| 39 | 0x01 | 1 | |
| 40 | 0x75 | Report Size | |
| 41 | 0x01 | 1비트 | |
| 42 | 0x95 | Report Count | |
| 43 | 0x03 | 3개 요소(Elements) | 3개 미디어 비트 |
| 44 | 0x81 | Input 태그 | |
| 45 | 0x02 | Data, Variable, Absolute | |
| 46 | 0xC0 | End Collection | 키보드 앱을 닫습니다 |
| 47 | 0x06 | Usage Page 태그(확장) | 2바이트 Usage Page |
| 48 | 0xFF, 0x00 | Page 0xFF00 | Vendor defined |
| 49 | 0x09 | Usage | |
| 50 | 0x01 | OID Event | 커스텀 Usage |
| 51 | 0xA1 | Collection | |
| 52 | 0x01 | Application | |
| 53 | 0x15 | Logical Minimum | |
| 54 | 0x00 | 0 | |
| 55 | 0x26 | Logical Maximum(확장) | 2바이트 Logical Max |
| 56 | 0xFF, 0x00 | 255 | |
| 57 | 0x75 | Report Size | |
| 58 | 0x08 | 8비트 | |
| 59 | 0x95 | Report Count | |
| 60 | 0x10 | 16개 요소(Elements) | 16바이트 OID 페이로드 |
| 61 | 0x09 | Usage | |
| 62 | 0x02 | OID Payload | |
| 63 | 0x91 | Output 태그 | |
| 64 | 0x02 | Data, Variable, Absolute | |
| 65 | 0xC0 | End Collection | Vendor Collection을 닫습니다 |
Q6/Q8의 나머지 바이트(66~91)는 바이트 정렬을 위한 패딩과 바이트 ID용 Feature Report입니다.
5. Boot 프로토콜 vs Report 프로토콜
리딩펜은 Protocol Mode 특성(UUID 0x2A4E on BLE, bInterfaceProtocol byte on USB)을 제공합니다. 두 가지 프로토콜이 유효합니다:
| 프로토콜(Protocol) | Report 레이아웃 | 사용 시점 |
|---|---|---|
| Boot | 8바이트 고정(modifier + reserved + 6 keycodes) | BIOS, 복구, 초기 부팅 |
| Report | Descriptor-defined | 일반 OS 사용 시 |
Q6/Q8 펌웨어 3.3.1에서는 기본값이 Report Protocol입니다. 호스트는 Protocol Mode 특성에 0x00을 기록하여 Boot Protocol을 요청할 수 있으며, 펌웨어는 16바이트 OID Report를 8바이트 Boot 포맷으로 축소하여 처리합니다. 데이터 손실은 없으며, OID Page ID와 코드는 단순히 키코드 바이트에 접혀 들어갑니다.
12,000대 규모의 학교 프로그램에서는 표준 HID 드라이버 스택이 1.4초 만에 로드되고 완전한 Report Protocol 파서는 4.8초가 걸리는 잠금된 환경의 Windows 이미지를 학교 노트북이 사용하므로, 기본적으로 Boot Protocol을 활성화할 것을 권장했습니다. 수업 시작 시 30명의 학생이 동시에 콜드부팅하는 상황에서 지연 시간 절감은 의미 있는 차이를 만듭니다.
6. Vendor-Defined Page 0xFF00: 예약한 이유
우리는 2022년에 USB-IF Vendor 목록에서 0xFF00을 예약했습니다. 이 예약은 해당 Page에서 0x01부터 0xFFFF까지의 모든 Usage ID를 정의할 수 있는 법적 권한을 부여하며, 당사의 펜과 상호 운용하려는 모든 제3자는 당사와 Usage ID 범위를 협의해야 합니다. ODM 고객은 자사 브랜드 ID에 32개 Usage의 하위 범위를 할당받으며, 그 중 처음 8개는 당사의 내부 사용용으로 예약되어 브랜드에는 24개의 Usage가 남습니다.
동일한 Q6/Q8 펌웨어가 230개 이상의 유통업체에 출하될 때 각 앱이 다른 유통업체의 펜과 실수로 통신하지 않도록 하는 것이 이러한 방식입니다.
7. OID Report 레이아웃
16바이트 OID 페이로드 Report는 다음과 같이 구성됩니다:
| Byte | 필드(Field) | 인코딩(Encoding) | 범위(Range) |
|---|---|---|---|
| 0–3 | Page ID | uint32 LE | 0 to 4,294,967,295 |
| 4–7 | OID Code | uint32 LE | 0 to 4,294,967,295 |
| 8 | 언어 | uint8 | 0–255 (언어 팩 참조) |
| 9–10 | 시퀀스 | uint16 LE | 0–65,535 |
| 11–12 | 체류 시간(ms) | uint16 LE | 0–65,535 |
| 13 | 플래그 | uint8 비트마스크 | bit 0 = repeat, bit 1 = audio busy |
| 14–15 | 예약 | uint16 LE | 0 |
플래그 바이트는 고객 브랜드가 재정의할 수 있는 유일한 항목입니다. 비트 2–7은 브랜드별 플래그(예: "테스트 모드", "진단", "공장 현장 QA")용으로 할당됩니다. 비트 0–1은 당사 펌웨어용으로 예약되어 있습니다.
8. Feature Report (Brand ID)
디스크립터 끝에 있는 4바이트 Feature Report가 브랜드 ID를 보유합니다. Feature Report는 호스트에서 읽기 전용이며, 공장 프로비저닝 중에 설정됩니다. 이는 두 가지 목적을 수행합니다:
- 컴패니언 앱은 브랜드 ID를 읽어 어느 콘텐츠 카탈로그를 로드할지 결정합니다.
- QA 스테이션은 브랜드 ID를 읽어 펌웨어 로드 무결성을 검증합니다.
0x09, 0x03, // Usage (Brand ID)
0x15, 0x00, // Logical Minimum 0
0x26, 0xFF, 0x00, // Logical Maximum 255
0x75, 0x08, // Report Size 8
0x95, 0x04, // Report Count 4
0xB1, 0x02, // Feature (Data, Variable, Absolute)
첫 번째 바이트는 브랜드 인덱스(현재 38개의 브랜드 인덱스가 할당됨), 두 번째는 지역 코드(1=EU, 2=US, 3=APAC, 4=MENA, 5=LATAM, 6=SSA), 세 번째는 콘텐츠 리비전(0–255), 네 번째는 처음 세 바이트에 대한 CRC입니다.
9. 디스크립터가 GATT에 매핑되는 방식
펜이 BLE에 있을 때, 동일한 디스크립터 바이트는 특성 0x2A4B(Report Descriptor)에서 제공됩니다. 리포트 자체는 알림을 통해 특성 0x2A4D에서 전송됩니다. 프로토콜 모드는 0x2A4E에 있습니다. 브랜드 ID Feature Report는 동일 HID 서비스의 벤더 정의 특성 0x9F01(읽기 전용)에 노출됩니다.
| USB HID Item | GATT Characteristic UUID | 속성 |
|---|---|---|
| Report Descriptor | 0x2A4B | Read |
| Report | 0x2A4D | Notify, Write, WriteWithoutResponse |
| Protocol Mode | 0x2A4E | Read, Write |
| Brand ID | 0x9F01 (custom) | Read |
| HID Information | 0x2A4A | Read |
이것은 TC-060에 문서화된 GATT 테이블입니다.
10. 모든 디스크립터 빌드에서 실행하는 검증 스위트
디스크립터가 출하되기 전에 다음을 통과해야 합니다:
| 검사 | 도구 | 합격 기준 |
|---|---|---|
| 파싱 적합성 | USB-IF HID 디스크립터 뷰어 | 오류 0건, 경고 0건 |
| 부트 프로토콜 부트 가능성 | USBlyzer | 부트 리포트를 성공적으로 수정 |
| OS 열거 | Windows 10/11 장치 관리자 | HID 키보드 + HID 벤더 디바이스로 목록 표시 |
| iPadOS 페어링 | iPad 9세대, iPadOS 17 | 페어링 및 디스크립터 읽기 성공 |
| ChromeOS 열거(Enumeration) | Lenovo Chromebook 14 | 입력 디바이스로 목록 표시 |
| macOS 열거(Enumeration) | MacBook Air M1 | 시스템 정보에 3개 인터페이스 모두 표시 |
| Android 열거(Enumeration) | Pixel 7a, Android 14 | InputDeviceManager reports keyboard + vendor |
이 중 하나라도 실패하면 디스크립터는 거부되고 펌웨어 빌드는 보류됩니다. 2023년 이후 86개의 펌웨어 릴리스 가운데 6개가 이 게이트에서 거부되어 롤백되었습니다. 가장 흔한 원인은 확장 길이 인코딩이 잘못된 Usage Page 태그로, macOS 검증에서는 실패하지만 Windows에서는 통과합니다.
11. 고객이 자주 하는 실수
ODM 고객이 자체 디스크립터 초안을 가져올 때, 거부 사례의 약 80%는 다음 세 가지 실수에서 비롯됩니다.
- Modifier 뒤의 예약 바이트를 누락하는 경우. macOS는 부트 키보드 리포트를 정확히 9바이트(8 modifier + 1 예약 + 6 키코드) 기대합니다. 고객이 이를 8바이트로 압축하면 Mac의 부트 프로토콜이 동작하지 않습니다.
- 사용
Logical Maximum 0x00FF을(를) 확장 태그 없이 사용하는 경우.단축(Short) 형식0x25 0xFF은(는) 최대 127 값까지는 동작하지만, 255까지의 값을 위해서는 확장 태그가 필수입니다. 고객이 이를 잊으면 디스크립터가 Linux에서 조용히 잘립니다. - Output 컬렉션 내부에 Input 리포트를 선언하는 경우. HID 파서는 Output 전용 컬렉션을 엄격히 강제하지 않지만 일부 OS 드라이버는 그렇게 합니다. 저희는 항상 OID 컬렉션에서 Output을 선언하여 대칭성을 명시적으로 표현합니다.
12. 성능 예산
호스트 측에서는 열거 시점에 디스크립터가 한 번 파싱됩니다. 디바이스 측에서는 모든 리포트 어셈블리가 디스크립터 맵에 접근합니다. Q6/Q8 펌웨어 3.3.1의 내부 벤치마크 기준:
| Operation | Cycles | 64 MHz에서의 시간 |
|---|---|---|
| Usage Page 조회 | 8 | 125 ns |
| Usage ID 조회 | 12 | 188 ns |
| 16바이트 OID 리포트 어셈블 | 240 | 3.75 µs |
| USB EP1 IN으로 리포트 전송 | 320 | 5.0 µs |
| BLE 0x2A4D로 리포트 전송 | 1,840 | 28.75 µs |
| 탭당 총합 (USB) | 580 | 9.1 µs |
| 탭당 총합 (BLE) | 2,100 | 32.8 µs |
9.1 µs USB 수치가 6.4 ms 엔드 투 엔드 레이턴시를 가능하게 하는 핵심입니다. BLE 수치는 32 ms BLE 레이턴시의 가장 큰 구성 요소입니다.
13. 더 큰 그림
HID 리포트 디스크립터를 공개적으로 공개하지 않는 펜을 평가 중이라면, 그것이 가장 먼저 요청해야 할 사항입니다. 디스크립터 공유를 거부하는 공급업체는 비준수 사항을 숨기고 있거나, 귀사 측에서 드라이버 작업이 필요한 비 HID 스택을 기반으로 구축하고 있는 경우입니다. Q6/Q8 펌웨어 3.3.1의 디스크립터는 92바이트이며, 자격을 갖춘 ODM 고객에게 요청 시 NDA 체결 하에 공개하고 있습니다.
15. 관련 자료
- 리딩펜 USB HID 및 GATT 기반 HID
- 리딩펜 블루투스 페어링 SSP(Secure Simple Pairing)
- 리딩펜 BLE 서비스 특성 UUID 설계
- 리딩펜 GATT 서버 및 알림 메커니즘
- 리딩펜 USB Type-C 충전 PD QC 및 BC1.2
- 리딩펜 블루투스 5.0 vs 5.2 vs 5.3
- 리딩펜 OTA 펌웨어 업데이트 메커니즘
- 리딩펜 사전 파일 형식 및 에디션