← Documents Documentation/usb/gadget_hid.rst GitHub 원문 ↗

Linux 6.18.37 · USB

Linux USB HID 가젯 드라이버

USB HID 가젯의 플랫폼·configfs 구성, /dev/hidgX 보고서 송수신, 키보드·마우스·조이스틱 시험 프로그램의 전체 동작을 설명합니다.

Source pathDocumentation/usb/gadget_hid.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

gadget_hid.rst:1-457

HID Gadget 드라이버는 커널이 USB HID 전송을 처리하고 사용자 공간이 `/dev/hidgX`에 정해진 크기의 보고서를 읽고 쓰는 구조입니다. 플랫폼 장치 또는 configfs로 보고서 설명자를 제공한 뒤 키보드·마우스·조이스틱 기능을 구성할 수 있습니다.

문서의 `hid_gadget_test` 예제는 표준 입력과 HID 장치를 `select()`로 동시에 감시합니다. 사용자 명령을 장치별 보고서로 바꾸어 전송하고, `--hold`가 없으면 0 보고서를 추가해 release를 표현하며, 호스트가 보낸 LED 상태 등의 출력 보고서도 16진수로 표시합니다.

실사용에서는 보고서 설명자의 `report_length`와 사용자 공간 프로그램이 쓰는 길이·바이트 배치가 반드시 일치해야 합니다. 또한 여러 HID function을 만들 때는 UDC의 interrupt endpoint 수가 실제 개수 제한이 됩니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ===========================
2 Linux USB HID gadget driver
3 ===========================
4
5 Introduction
6 ============
7
8 The HID Gadget driver provides emulation of USB Human Interface
9 Devices (HID). The basic HID handling is done in the kernel,
10 and HID reports can be sent/received through I/O on the
11 /dev/hidgX character devices.
12
13 For more details about HID, see the developer page on
14 https://www.usb.org/developers/hidpage/
15
16 Configuration
17 =============
18
19 g_hid is a platform driver, so to use it you need to add
20 struct platform_device(s) to your platform code defining the
21 HID function descriptors you want to use - E.G. something
22 like::
23
24 #include <linux/platform_device.h>
25 #include <linux/usb/g_hid.h>
26
27 /* hid descriptor for a keyboard */
28 static struct hidg_func_descriptor my_hid_data = {
29 .subclass = 0, /* No subclass */
30 .protocol = 1, /* Keyboard */
31 .report_length = 8,
32 .report_desc_length = 63,
33 .report_desc = {
34 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
35 0x09, 0x06, /* USAGE (Keyboard) */
36 0xa1, 0x01, /* COLLECTION (Application) */
37 0x05, 0x07, /* USAGE_PAGE (Keyboard) */
38 0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */
39 0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */
40 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
41 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
42 0x75, 0x01, /* REPORT_SIZE (1) */
43 0x95, 0x08, /* REPORT_COUNT (8) */
44 0x81, 0x02, /* INPUT (Data,Var,Abs) */
45 0x95, 0x01, /* REPORT_COUNT (1) */
46 0x75, 0x08, /* REPORT_SIZE (8) */
47 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */
48 0x95, 0x05, /* REPORT_COUNT (5) */
49 0x75, 0x01, /* REPORT_SIZE (1) */
50 0x05, 0x08, /* USAGE_PAGE (LEDs) */
51 0x19, 0x01, /* USAGE_MINIMUM (Num Lock) */
52 0x29, 0x05, /* USAGE_MAXIMUM (Kana) */
53 0x91, 0x02, /* OUTPUT (Data,Var,Abs) */
54 0x95, 0x01, /* REPORT_COUNT (1) */
55 0x75, 0x03, /* REPORT_SIZE (3) */
56 0x91, 0x03, /* OUTPUT (Cnst,Var,Abs) */
57 0x95, 0x06, /* REPORT_COUNT (6) */
58 0x75, 0x08, /* REPORT_SIZE (8) */
59 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
60 0x25, 0x65, /* LOGICAL_MAXIMUM (101) */
61 0x05, 0x07, /* USAGE_PAGE (Keyboard) */
62 0x19, 0x00, /* USAGE_MINIMUM (Reserved) */
63 0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */
64 0x81, 0x00, /* INPUT (Data,Ary,Abs) */
65 0xc0 /* END_COLLECTION */
66 }
67 };
68
69 static struct platform_device my_hid = {
70 .name = "hidg",
71 .id = 0,
72 .num_resources = 0,
73 .resource = 0,
74 .dev.platform_data = &my_hid_data,
75 };
76
77 You can add as many HID functions as you want, only limited by
78 the amount of interrupt endpoints your gadget driver supports.
79
80 Configuration with configfs
81 ===========================
82
83 Instead of adding fake platform devices and drivers in order to pass
84 some data to the kernel, if HID is a part of a gadget composed with
85 configfs the hidg_func_descriptor.report_desc is passed to the kernel
86 by writing the appropriate stream of bytes to a configfs attribute.
87
88 Send and receive HID reports
89 ============================
90
91 HID reports can be sent/received using read/write on the
92 /dev/hidgX character devices. See below for an example program
93 to do this.
94
95 hid_gadget_test is a small interactive program to test the HID
96 gadget driver. To use, point it at a hidg device and set the
97 device type (keyboard / mouse / joystick) - E.G.::
98
99 # hid_gadget_test /dev/hidg0 keyboard
100
101 You are now in the prompt of hid_gadget_test. You can type any
102 combination of options and values. Available options and
103 values are listed at program start. In keyboard mode you can
104 send up to six values.
105
106 For example type: g i s t r --left-shift
107
108 Hit return and the corresponding report will be sent by the
109 HID gadget.
110
111 Another interesting example is the caps lock test. Type
112 --caps-lock and hit return. A report is then sent by the
113 gadget and you should receive the host answer, corresponding
114 to the caps lock LED status::
115
116 --caps-lock
117 recv report:2
118
119 With this command::
120
121 # hid_gadget_test /dev/hidg1 mouse
122
123 You can test the mouse emulation. Values are two signed numbers.
124
125
126 Sample code::
127
128 /* hid_gadget_test */
129
130 #include <pthread.h>
131 #include <string.h>
132 #include <stdio.h>
133 #include <ctype.h>
134 #include <fcntl.h>
135 #include <errno.h>
136 #include <stdio.h>
137 #include <stdlib.h>
138 #include <unistd.h>
139
140 #define BUF_LEN 512
141
142 struct options {
143 const char *opt;
144 unsigned char val;
145 };
146
147 static struct options kmod[] = {
148 {.opt = "--left-ctrl", .val = 0x01},
149 {.opt = "--right-ctrl", .val = 0x10},
150 {.opt = "--left-shift", .val = 0x02},
151 {.opt = "--right-shift", .val = 0x20},
152 {.opt = "--left-alt", .val = 0x04},
153 {.opt = "--right-alt", .val = 0x40},
154 {.opt = "--left-meta", .val = 0x08},
155 {.opt = "--right-meta", .val = 0x80},
156 {.opt = NULL}
157 };
158
159 static struct options kval[] = {
160 {.opt = "--return", .val = 0x28},
161 {.opt = "--esc", .val = 0x29},
162 {.opt = "--bckspc", .val = 0x2a},
163 {.opt = "--tab", .val = 0x2b},
164 {.opt = "--spacebar", .val = 0x2c},
165 {.opt = "--caps-lock", .val = 0x39},
166 {.opt = "--f1", .val = 0x3a},
167 {.opt = "--f2", .val = 0x3b},
168 {.opt = "--f3", .val = 0x3c},
169 {.opt = "--f4", .val = 0x3d},
170 {.opt = "--f5", .val = 0x3e},
171 {.opt = "--f6", .val = 0x3f},
172 {.opt = "--f7", .val = 0x40},
173 {.opt = "--f8", .val = 0x41},
174 {.opt = "--f9", .val = 0x42},
175 {.opt = "--f10", .val = 0x43},
176 {.opt = "--f11", .val = 0x44},
177 {.opt = "--f12", .val = 0x45},
178 {.opt = "--insert", .val = 0x49},
179 {.opt = "--home", .val = 0x4a},
180 {.opt = "--pageup", .val = 0x4b},
181 {.opt = "--del", .val = 0x4c},
182 {.opt = "--end", .val = 0x4d},
183 {.opt = "--pagedown", .val = 0x4e},
184 {.opt = "--right", .val = 0x4f},
185 {.opt = "--left", .val = 0x50},
186 {.opt = "--down", .val = 0x51},
187 {.opt = "--kp-enter", .val = 0x58},
188 {.opt = "--up", .val = 0x52},
189 {.opt = "--num-lock", .val = 0x53},
190 {.opt = NULL}
191 };
192
193 int keyboard_fill_report(char report[8], char buf[BUF_LEN], int *hold)
194 {
195 char *tok = strtok(buf, " ");
196 int key = 0;
197 int i = 0;
198
199 for (; tok != NULL; tok = strtok(NULL, " ")) {
200
201 if (strcmp(tok, "--quit") == 0)
202 return -1;
203
204 if (strcmp(tok, "--hold") == 0) {
205 *hold = 1;
206 continue;
207 }
208
209 if (key < 6) {
210 for (i = 0; kval[i].opt != NULL; i++)
211 if (strcmp(tok, kval[i].opt) == 0) {
212 report[2 + key++] = kval[i].val;
213 break;
214 }
215 if (kval[i].opt != NULL)
216 continue;
217 }
218
219 if (key < 6)
220 if (islower(tok[0])) {
221 report[2 + key++] = (tok[0] - ('a' - 0x04));
222 continue;
223 }
224
225 for (i = 0; kmod[i].opt != NULL; i++)
226 if (strcmp(tok, kmod[i].opt) == 0) {
227 report[0] = report[0] | kmod[i].val;
228 break;
229 }
230 if (kmod[i].opt != NULL)
231 continue;
232
233 if (key < 6)
234 fprintf(stderr, "unknown option: %s\n", tok);
235 }
236 return 8;
237 }
238
239 static struct options mmod[] = {
240 {.opt = "--b1", .val = 0x01},
241 {.opt = "--b2", .val = 0x02},
242 {.opt = "--b3", .val = 0x04},
243 {.opt = NULL}
244 };
245
246 int mouse_fill_report(char report[8], char buf[BUF_LEN], int *hold)
247 {
248 char *tok = strtok(buf, " ");
249 int mvt = 0;
250 int i = 0;
251 for (; tok != NULL; tok = strtok(NULL, " ")) {
252
253 if (strcmp(tok, "--quit") == 0)
254 return -1;
255
256 if (strcmp(tok, "--hold") == 0) {
257 *hold = 1;
258 continue;
259 }
260
261 for (i = 0; mmod[i].opt != NULL; i++)
262 if (strcmp(tok, mmod[i].opt) == 0) {
263 report[0] = report[0] | mmod[i].val;
264 break;
265 }
266 if (mmod[i].opt != NULL)
267 continue;
268
269 if (!(tok[0] == '-' && tok[1] == '-') && mvt < 2) {
270 errno = 0;
271 report[1 + mvt++] = (char)strtol(tok, NULL, 0);
272 if (errno != 0) {
273 fprintf(stderr, "Bad value:'%s'\n", tok);
274 report[1 + mvt--] = 0;
275 }
276 continue;
277 }
278
279 fprintf(stderr, "unknown option: %s\n", tok);
280 }
281 return 3;
282 }
283
284 static struct options jmod[] = {
285 {.opt = "--b1", .val = 0x10},
286 {.opt = "--b2", .val = 0x20},
287 {.opt = "--b3", .val = 0x40},
288 {.opt = "--b4", .val = 0x80},
289 {.opt = "--hat1", .val = 0x00},
290 {.opt = "--hat2", .val = 0x01},
291 {.opt = "--hat3", .val = 0x02},
292 {.opt = "--hat4", .val = 0x03},
293 {.opt = "--hatneutral", .val = 0x04},
294 {.opt = NULL}
295 };
296
297 int joystick_fill_report(char report[8], char buf[BUF_LEN], int *hold)
298 {
299 char *tok = strtok(buf, " ");
300 int mvt = 0;
301 int i = 0;
302
303 *hold = 1;
304
305 /* set default hat position: neutral */
306 report[3] = 0x04;
307
308 for (; tok != NULL; tok = strtok(NULL, " ")) {
309
310 if (strcmp(tok, "--quit") == 0)
311 return -1;
312
313 for (i = 0; jmod[i].opt != NULL; i++)
314 if (strcmp(tok, jmod[i].opt) == 0) {
315 report[3] = (report[3] & 0xF0) | jmod[i].val;
316 break;
317 }
318 if (jmod[i].opt != NULL)
319 continue;
320
321 if (!(tok[0] == '-' && tok[1] == '-') && mvt < 3) {
322 errno = 0;
323 report[mvt++] = (char)strtol(tok, NULL, 0);
324 if (errno != 0) {
325 fprintf(stderr, "Bad value:'%s'\n", tok);
326 report[mvt--] = 0;
327 }
328 continue;
329 }
330
331 fprintf(stderr, "unknown option: %s\n", tok);
332 }
333 return 4;
334 }
335
336 void print_options(char c)
337 {
338 int i = 0;
339
340 if (c == 'k') {
341 printf(" keyboard options:\n"
342 " --hold\n");
343 for (i = 0; kmod[i].opt != NULL; i++)
344 printf("\t\t%s\n", kmod[i].opt);
345 printf("\n keyboard values:\n"
346 " [a-z] or\n");
347 for (i = 0; kval[i].opt != NULL; i++)
348 printf("\t\t%-8s%s", kval[i].opt, i % 2 ? "\n" : "");
349 printf("\n");
350 } else if (c == 'm') {
351 printf(" mouse options:\n"
352 " --hold\n");
353 for (i = 0; mmod[i].opt != NULL; i++)
354 printf("\t\t%s\n", mmod[i].opt);
355 printf("\n mouse values:\n"
356 " Two signed numbers\n"
357 "--quit to close\n");
358 } else {
359 printf(" joystick options:\n");
360 for (i = 0; jmod[i].opt != NULL; i++)
361 printf("\t\t%s\n", jmod[i].opt);
362 printf("\n joystick values:\n"
363 " three signed numbers\n"
364 "--quit to close\n");
365 }
366 }
367
368 int main(int argc, const char *argv[])
369 {
370 const char *filename = NULL;
371 int fd = 0;
372 char buf[BUF_LEN];
373 int cmd_len;
374 char report[8];
375 int to_send = 8;
376 int hold = 0;
377 fd_set rfds;
378 int retval, i;
379
380 if (argc < 3) {
381 fprintf(stderr, "Usage: %s devname mouse|keyboard|joystick\n",
382 argv[0]);
383 return 1;
384 }
385
386 if (argv[2][0] != 'k' && argv[2][0] != 'm' && argv[2][0] != 'j')
387 return 2;
388
389 filename = argv[1];
390
391 if ((fd = open(filename, O_RDWR, 0666)) == -1) {
392 perror(filename);
393 return 3;
394 }
395
396 print_options(argv[2][0]);
397
398 while (42) {
399
400 FD_ZERO(&rfds);
401 FD_SET(STDIN_FILENO, &rfds);
402 FD_SET(fd, &rfds);
403
404 retval = select(fd + 1, &rfds, NULL, NULL, NULL);
405 if (retval == -1 && errno == EINTR)
406 continue;
407 if (retval < 0) {
408 perror("select()");
409 return 4;
410 }
411
412 if (FD_ISSET(fd, &rfds)) {
413 cmd_len = read(fd, buf, BUF_LEN - 1);
414 printf("recv report:");
415 for (i = 0; i < cmd_len; i++)
416 printf(" %02x", buf[i]);
417 printf("\n");
418 }
419
420 if (FD_ISSET(STDIN_FILENO, &rfds)) {
421 memset(report, 0x0, sizeof(report));
422 cmd_len = read(STDIN_FILENO, buf, BUF_LEN - 1);
423
424 if (cmd_len == 0)
425 break;
426
427 buf[cmd_len - 1] = '\0';
428 hold = 0;
429
430 memset(report, 0x0, sizeof(report));
431 if (argv[2][0] == 'k')
432 to_send = keyboard_fill_report(report, buf, &hold);
433 else if (argv[2][0] == 'm')
434 to_send = mouse_fill_report(report, buf, &hold);
435 else
436 to_send = joystick_fill_report(report, buf, &hold);
437
438 if (to_send == -1)
439 break;
440
441 if (write(fd, report, to_send) != to_send) {
442 perror(filename);
443 return 5;
444 }
445 if (!hold) {
446 memset(report, 0x0, sizeof(report));
447 if (write(fd, report, to_send) != to_send) {
448 perror(filename);
449 return 6;
450 }
451 }
452 }
453 }
454
455 close(fd);
456 return 0;
457 }
458

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

소개

1-15

HID Gadget 드라이버는 USB Human Interface Device(HID)를 에뮬레이션합니다. HID 프로토콜의 기본 처리는 커널이 담당하고, 사용자 공간은 `/dev/hidgX` 문자 장치를 읽고 쓰는 방식으로 HID 보고서를 송수신합니다.

따라서 가젯 쪽 프로그램은 USB 전송 자체를 직접 구현하지 않고 보고서 형식에 맞는 바이트를 문자 장치에 기록하거나 호스트가 보낸 출력 보고서를 읽으면 됩니다.

HID 규격의 자세한 내용은 원문에 제시된 USB-IF 개발자 페이지 `https://www.usb.org/developers/hidpage/`를 참고합니다.

HID 가젯의 데이터 경로
사용자 공간이 HID 보고서 바이트를 구성프로그램이 /dev/hidgX에 보고서를 write커널 HID Gadget 드라이버가 USB 전송을 처리USB 호스트가 키보드·마우스·조이스틱 입력으로 해석호스트의 출력 보고서는 /dev/hidgX read로 사용자 공간에 전달

커널의 HID 처리와 사용자 공간 문자 장치 사이의 역할 분담입니다.

===========================
Linux USB HID gadget driver
===========================

Introduction
============

The HID Gadget driver provides emulation of USB Human Interface
Devices (HID). The basic HID handling is done in the kernel,
and HID reports can be sent/received through I/O on the
/dev/hidgX character devices.

For more details about HID, see the developer page on
https://www.usb.org/developers/hidpage/

플랫폼 드라이버 구성

16-22

`g_hid`는 플랫폼 드라이버입니다. 사용하려면 플랫폼 코드에 하나 이상의 `struct platform_device`를 추가하고, 사용할 HID 기능 설명자를 각 장치의 플랫폼 데이터로 제공해야 합니다.

설명자는 HID subclass와 protocol, 한 보고서의 길이, 보고서 설명자의 길이와 실제 바이트 배열을 정의합니다.

이어지는 코드는 8바이트 입력 보고서를 사용하는 키보드 HID 기능을 등록하는 예입니다.

Configuration
=============

g_hid is a platform driver, so to use it you need to add
struct platform_device(s) to your platform code defining the
HID function descriptors you want to use - E.G. something
like::

키보드 설명자와 플랫폼 장치

23-79

`my_hid_data`의 `protocol = 1`은 키보드 프로토콜을 선택하고, `report_length = 8`은 한 번에 주고받는 보고서 크기를 8바이트로 지정합니다. `report_desc_length = 63`은 뒤따르는 보고서 설명자 배열의 길이입니다.

보고서 설명자는 Generic Desktop의 Keyboard application collection을 선언합니다. 첫 바이트에는 LeftControl부터 Right GUI까지 여덟 modifier 비트를 배치하고, 다음 바이트는 상수로 예약합니다.

호스트가 장치로 보내는 LED 출력 보고서는 Num Lock부터 Kana까지 다섯 비트와 세 개의 상수 비트로 구성됩니다. 키 입력 영역은 최대 여섯 개의 8비트 key usage를 담습니다.

`my_hid` 플랫폼 장치는 이름을 `hidg`, 인스턴스 ID를 0으로 지정하고 `dev.platform_data`가 `my_hid_data`를 가리키게 합니다. 커널은 이 연결을 통해 해당 HID 기능의 설명자를 얻습니다.

HID 기능은 여러 개 추가할 수 있지만, 실제 상한은 가젯 컨트롤러가 제공하는 interrupt endpoint 수입니다.

키보드 HID 설명자 핵심 필드
필드예제 값의미
subclass0별도 subclass 없음
protocol1키보드 프로토콜
report_length8HID 보고서 길이 8바이트
report_desc_length63보고서 설명자 길이
report_desc63-byte arraymodifier·LED·키 usage 배치

예제의 구조체 필드와 의미를 정리했습니다.

8바이트 키보드 입력 보고서
바이트내용
0Ctrl·Shift·Alt·GUI modifier 비트
1예약된 상수 바이트
2첫 번째 key usage
3두 번째 key usage
4세 번째 key usage
5네 번째 key usage
6다섯 번째 key usage
7여섯 번째 key usage

예제 설명자가 정의하는 입력 보고서의 바이트 배치입니다.


  #include <linux/platform_device.h>
  #include <linux/usb/g_hid.h>

  /* hid descriptor for a keyboard */
  static struct hidg_func_descriptor my_hid_data = {
	.subclass		= 0, /* No subclass */
	.protocol		= 1, /* Keyboard */
	.report_length		= 8,
	.report_desc_length	= 63,
	.report_desc		= {
		0x05, 0x01,	/* USAGE_PAGE (Generic Desktop)	          */
		0x09, 0x06,	/* USAGE (Keyboard)                       */
		0xa1, 0x01,	/* COLLECTION (Application)               */
		0x05, 0x07,	/*   USAGE_PAGE (Keyboard)                */
		0x19, 0xe0,	/*   USAGE_MINIMUM (Keyboard LeftControl) */
		0x29, 0xe7,	/*   USAGE_MAXIMUM (Keyboard Right GUI)   */
		0x15, 0x00,	/*   LOGICAL_MINIMUM (0)                  */
		0x25, 0x01,	/*   LOGICAL_MAXIMUM (1)                  */
		0x75, 0x01,	/*   REPORT_SIZE (1)                      */
		0x95, 0x08,	/*   REPORT_COUNT (8)                     */
		0x81, 0x02,	/*   INPUT (Data,Var,Abs)                 */
		0x95, 0x01,	/*   REPORT_COUNT (1)                     */
		0x75, 0x08,	/*   REPORT_SIZE (8)                      */
		0x81, 0x03,	/*   INPUT (Cnst,Var,Abs)                 */
		0x95, 0x05,	/*   REPORT_COUNT (5)                     */
		0x75, 0x01,	/*   REPORT_SIZE (1)                      */
		0x05, 0x08,	/*   USAGE_PAGE (LEDs)                    */
		0x19, 0x01,	/*   USAGE_MINIMUM (Num Lock)             */
		0x29, 0x05,	/*   USAGE_MAXIMUM (Kana)                 */
		0x91, 0x02,	/*   OUTPUT (Data,Var,Abs)                */
		0x95, 0x01,	/*   REPORT_COUNT (1)                     */
		0x75, 0x03,	/*   REPORT_SIZE (3)                      */
		0x91, 0x03,	/*   OUTPUT (Cnst,Var,Abs)                */
		0x95, 0x06,	/*   REPORT_COUNT (6)                     */
		0x75, 0x08,	/*   REPORT_SIZE (8)                      */
		0x15, 0x00,	/*   LOGICAL_MINIMUM (0)                  */
		0x25, 0x65,	/*   LOGICAL_MAXIMUM (101)                */
		0x05, 0x07,	/*   USAGE_PAGE (Keyboard)                */
		0x19, 0x00,	/*   USAGE_MINIMUM (Reserved)             */
		0x29, 0x65,	/*   USAGE_MAXIMUM (Keyboard Application) */
		0x81, 0x00,	/*   INPUT (Data,Ary,Abs)                 */
		0xc0		/* END_COLLECTION                         */
	}
  };

  static struct platform_device my_hid = {
	.name			= "hidg",
	.id			= 0,
	.num_resources		= 0,
	.resource		= 0,
	.dev.platform_data	= &my_hid_data,
  };

You can add as many HID functions as you want, only limited by
the amount of interrupt endpoints your gadget driver supports.

configfs를 이용한 구성

80-87

configfs로 조합한 가젯의 일부로 HID를 사용할 때는 데이터를 커널에 넘기기 위한 가짜 플랫폼 장치와 드라이버를 추가할 필요가 없습니다.

대신 `hidg_func_descriptor.report_desc`에 해당하는 보고서 설명자 바이트 스트림을 적절한 configfs 속성에 기록합니다. 커널은 그 속성에서 HID 기능 설명자를 구성합니다.

이 방식은 HID 기능을 다른 USB function과 함께 configfs 가젯 구성 안에서 동적으로 조합할 때 사용합니다.

configfs HID 설명자 전달
configfs에서 USB gadget과 HID function 생성HID report descriptor 바이트 스트림 준비해당 configfs attribute에 바이트 기록커널이 hidg_func_descriptor.report_desc로 수신HID function을 configuration에 연결하고 UDC에 bind

플랫폼 데이터 대신 configfs 속성으로 보고서 설명자를 넘기는 경로입니다.

Configuration with configfs
===========================

Instead of adding fake platform devices and drivers in order to pass
some data to the kernel, if HID is a part of a gadget composed with
configfs the hidg_func_descriptor.report_desc is passed to the kernel
by writing the appropriate stream of bytes to a configfs attribute.

HID 보고서 송수신

88-104

HID 보고서는 `/dev/hidgX` 문자 장치에 대한 `read`와 `write`로 송수신합니다. 아래의 예제 프로그램은 이 인터페이스를 직접 사용합니다.

`hid_gadget_test`는 HID Gadget 드라이버를 시험하는 작은 대화형 프로그램입니다. 첫 번째 인수로 `hidg` 장치 경로를, 두 번째 인수로 `keyboard`, `mouse`, `joystick` 중 하나를 지정합니다.

예를 들어 `hid_gadget_test /dev/hidg0 keyboard`를 실행하면 키보드 모드 프롬프트가 열립니다. 프로그램 시작 시 사용할 수 있는 option과 value 목록이 출력됩니다.

키보드 모드에서는 한 보고서에 최대 여섯 개의 일반 키 값을 넣을 수 있으며 modifier option은 별도의 첫 바이트에 결합됩니다.

hid_gadget_test 실행 모드
모드실행 예입력 형식
keyboard/dev/hidg0 keyboard최대 6개 키와 modifier
mouse/dev/hidg1 mouse버튼 option과 signed 이동값 2개
joystickhidgX joystick버튼·hat option과 signed 값 3개

장치 유형에 따라 보고서 작성 함수와 입력 형식이 달라집니다.

Send and receive HID reports
============================

HID reports can be sent/received using read/write on the
/dev/hidgX character devices. See below for an example program
to do this.

hid_gadget_test is a small interactive program to test the HID
gadget driver. To use, point it at a hidg device and set the
device type (keyboard / mouse / joystick) - E.G.::

	# hid_gadget_test /dev/hidg0 keyboard

You are now in the prompt of hid_gadget_test. You can type any
combination of options and values. Available options and
values are listed at program start. In keyboard mode you can
send up to six values.

대화형 시험 예

105-125

프롬프트에서 `g i s t r --left-shift`를 입력하고 Return을 누르면 해당 키와 왼쪽 Shift modifier가 들어간 보고서를 가젯이 호스트로 보냅니다.

Caps Lock 시험에서는 `--caps-lock`을 입력합니다. 가젯이 Caps Lock key report를 보낸 뒤 호스트가 LED 상태를 출력 보고서로 돌려주며, 예제는 이를 `recv report:2`처럼 표시합니다.

마우스 에뮬레이션은 `hid_gadget_test /dev/hidg1 mouse`로 실행합니다. 마우스 이동값은 두 개의 signed number로 입력합니다.

이 왕복 시험은 장치에서 호스트로 가는 입력 보고서뿐 아니라 호스트에서 장치로 오는 LED 출력 보고서까지 `/dev/hidgX`로 전달되는지 확인합니다.

Caps Lock 왕복 시험
사용자가 --caps-lock 입력hid_gadget_test가 Caps Lock key usage 보고서 생성보고서를 /dev/hidgX에 write호스트가 Caps Lock 상태를 변경호스트 LED 출력 보고서를 read하여 recv report로 표시

키 입력 보고서와 LED 출력 보고서가 반대 방향으로 이동합니다.


For example type: g i s t r --left-shift

Hit return and the corresponding report will be sent by the
HID gadget.

Another interesting example is the caps lock test. Type
--caps-lock and hit return. A report is then sent by the
gadget and you should receive the host answer, corresponding
to the caps lock LED status::

	--caps-lock
	recv report:2

With this command::

	# hid_gadget_test /dev/hidg1 mouse

You can test the mouse emulation. Values are two signed numbers.

예제 코드의 공통 정의와 modifier

126-158

예제는 문자열 처리, 문자 분류, 파일 I/O, 오류 처리, 표준 라이브러리와 POSIX 호출에 필요한 헤더를 포함하고 입력 버퍼 크기를 `BUF_LEN` 512로 정합니다.

`struct options`는 명령행 option 문자열과 보고서에 기록할 1바이트 값을 묶습니다. 각 option 배열은 `opt = NULL` 항목으로 끝납니다.

`kmod`는 왼쪽·오른쪽 Ctrl, Shift, Alt, Meta를 키보드 보고서 첫 바이트의 비트 값에 대응시킵니다. 여러 modifier는 bitwise OR로 동시에 표현할 수 있습니다.

키보드 modifier 비트
Option
--left-ctrl0x01Left Ctrl
--left-shift0x02Left Shift
--left-alt0x04Left Alt
--left-meta0x08Left GUI/Meta
--right-ctrl0x10Right Ctrl
--right-shift0x20Right Shift
--right-alt0x40Right Alt
--right-meta0x80Right GUI/Meta

`kmod` 배열의 option과 첫 보고서 바이트 비트입니다.

Sample code::

    /* hid_gadget_test */

    #include <pthread.h>
    #include <string.h>
    #include <stdio.h>
    #include <ctype.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>

    #define BUF_LEN 512

    struct options {
	const char    *opt;
	unsigned char val;
  };

  static struct options kmod[] = {
	{.opt = "--left-ctrl",		.val = 0x01},
	{.opt = "--right-ctrl",		.val = 0x10},
	{.opt = "--left-shift",		.val = 0x02},
	{.opt = "--right-shift",	.val = 0x20},
	{.opt = "--left-alt",		.val = 0x04},
	{.opt = "--right-alt",		.val = 0x40},
	{.opt = "--left-meta",		.val = 0x08},
	{.opt = "--right-meta",		.val = 0x80},
	{.opt = NULL}
  };

키보드 usage 값 표

159-192

`kval` 배열은 Return, Escape, Backspace, Tab, Space, Caps Lock, F1~F12, 편집·이동 키와 Num Lock option을 HID Keyboard/Keypad usage 값에 대응시킵니다.

알파벳 소문자는 배열을 거치지 않고 `a`를 usage `0x04`로 하는 연속 범위를 이용해 계산합니다. 따라서 일반 문자와 특수 key option을 같은 입력 줄에서 섞을 수 있습니다.

배열의 마지막 `NULL` sentinel은 검색 루프의 종료 조건이며, 일치 여부를 판별할 때도 사용됩니다.

  static struct options kval[] = {
	{.opt = "--return",	.val = 0x28},
	{.opt = "--esc",	.val = 0x29},
	{.opt = "--bckspc",	.val = 0x2a},
	{.opt = "--tab",	.val = 0x2b},
	{.opt = "--spacebar",	.val = 0x2c},
	{.opt = "--caps-lock",	.val = 0x39},
	{.opt = "--f1",		.val = 0x3a},
	{.opt = "--f2",		.val = 0x3b},
	{.opt = "--f3",		.val = 0x3c},
	{.opt = "--f4",		.val = 0x3d},
	{.opt = "--f5",		.val = 0x3e},
	{.opt = "--f6",		.val = 0x3f},
	{.opt = "--f7",		.val = 0x40},
	{.opt = "--f8",		.val = 0x41},
	{.opt = "--f9",		.val = 0x42},
	{.opt = "--f10",	.val = 0x43},
	{.opt = "--f11",	.val = 0x44},
	{.opt = "--f12",	.val = 0x45},
	{.opt = "--insert",	.val = 0x49},
	{.opt = "--home",	.val = 0x4a},
	{.opt = "--pageup",	.val = 0x4b},
	{.opt = "--del",	.val = 0x4c},
	{.opt = "--end",	.val = 0x4d},
	{.opt = "--pagedown",	.val = 0x4e},
	{.opt = "--right",	.val = 0x4f},
	{.opt = "--left",	.val = 0x50},
	{.opt = "--down",	.val = 0x51},
	{.opt = "--kp-enter",	.val = 0x58},
	{.opt = "--up",		.val = 0x52},
	{.opt = "--num-lock",	.val = 0x53},
	{.opt = NULL}
  };

keyboard_fill_report

193-238

`keyboard_fill_report()`는 공백으로 분리한 token을 순회하면서 8바이트 키보드 보고서를 채웁니다. `--quit`를 만나면 `-1`을 반환하여 주 루프 종료를 요청합니다.

`--hold`는 `hold`를 1로 만들어 이번 보고서 뒤에 자동 key-release 보고서를 보내지 않게 합니다. key slot은 여섯 개로 제한되며 `kval` option이나 소문자를 `report[2]` 이후에 순서대로 기록합니다.

modifier option은 `kmod`에서 찾아 `report[0]`에 OR합니다. 인식하지 못한 token은 여섯 key slot이 아직 남아 있을 때 표준 오류로 알립니다.

완성된 키보드 보고서의 길이는 항상 8바이트이므로 함수는 정상 경로에서 8을 반환합니다.

키보드 보고서 작성 순서
입력 줄을 공백 token으로 분리--quit와 --hold 제어 option 확인kval 배열에서 특수 키 검색소문자를 HID usage로 계산kmod modifier를 report[0]에 OR최대 6개 key를 report[2..7]에 기록하고 8 반환

각 token을 key value 또는 modifier로 분류하는 흐름입니다.

  int keyboard_fill_report(char report[8], char buf[BUF_LEN], int *hold)
  {
	char *tok = strtok(buf, " ");
	int key = 0;
	int i = 0;

	for (; tok != NULL; tok = strtok(NULL, " ")) {

		if (strcmp(tok, "--quit") == 0)
			return -1;

		if (strcmp(tok, "--hold") == 0) {
			*hold = 1;
			continue;
		}

		if (key < 6) {
			for (i = 0; kval[i].opt != NULL; i++)
				if (strcmp(tok, kval[i].opt) == 0) {
					report[2 + key++] = kval[i].val;
					break;
				}
			if (kval[i].opt != NULL)
				continue;
		}

		if (key < 6)
			if (islower(tok[0])) {
				report[2 + key++] = (tok[0] - ('a' - 0x04));
				continue;
			}

		for (i = 0; kmod[i].opt != NULL; i++)
			if (strcmp(tok, kmod[i].opt) == 0) {
				report[0] = report[0] | kmod[i].val;
				break;
			}
		if (kmod[i].opt != NULL)
			continue;

		if (key < 6)
			fprintf(stderr, "unknown option: %s\n", tok);
	}
	return 8;
  }

mouse_fill_report

239-283

`mmod` 배열은 세 마우스 버튼 `--b1`, `--b2`, `--b3`를 각각 `0x01`, `0x02`, `0x04` 비트에 대응시킵니다.

`mouse_fill_report()`도 `--quit`와 `--hold`를 처리하고, 버튼 option은 `report[0]`에 OR합니다.

option 형태가 아닌 token은 최대 두 개까지 `strtol()`로 변환해 `report[1]`과 `report[2]`에 signed 이동량으로 저장합니다. 변환 오류가 발생하면 메시지를 출력하고 해당 값을 0으로 되돌립니다.

마우스 보고서는 버튼 1바이트와 두 축 이동량 2바이트로 이루어지므로 함수는 3을 반환합니다.

마우스 보고서 배치
바이트내용
0버튼 1~3 비트
1첫 번째 signed 이동값
2두 번째 signed 이동값

예제의 3바이트 마우스 보고서입니다.

  static struct options mmod[] = {
	{.opt = "--b1", .val = 0x01},
	{.opt = "--b2", .val = 0x02},
	{.opt = "--b3", .val = 0x04},
	{.opt = NULL}
  };

  int mouse_fill_report(char report[8], char buf[BUF_LEN], int *hold)
  {
	char *tok = strtok(buf, " ");
	int mvt = 0;
	int i = 0;
	for (; tok != NULL; tok = strtok(NULL, " ")) {

		if (strcmp(tok, "--quit") == 0)
			return -1;

		if (strcmp(tok, "--hold") == 0) {
			*hold = 1;
			continue;
		}

		for (i = 0; mmod[i].opt != NULL; i++)
			if (strcmp(tok, mmod[i].opt) == 0) {
				report[0] = report[0] | mmod[i].val;
				break;
			}
		if (mmod[i].opt != NULL)
			continue;

		if (!(tok[0] == '-' && tok[1] == '-') && mvt < 2) {
			errno = 0;
			report[1 + mvt++] = (char)strtol(tok, NULL, 0);
			if (errno != 0) {
				fprintf(stderr, "Bad value:'%s'\n", tok);
				report[1 + mvt--] = 0;
			}
			continue;
		}

		fprintf(stderr, "unknown option: %s\n", tok);
	}
	return 3;
  }

joystick_fill_report

284-335

`jmod`는 네 버튼을 상위 nibble의 `0x10`~`0x80`에, 네 hat 방향과 중립 상태를 하위 nibble의 `0x00`~`0x04`에 대응시킵니다.

`joystick_fill_report()`는 상태 유지가 기본이므로 시작하자마자 `hold = 1`로 설정하고 hat 기본 위치를 neutral인 `0x04`로 둡니다.

hat option을 적용할 때는 `report[3]`의 상위 nibble을 보존하고 하위 nibble만 바꿉니다. option이 아닌 token은 최대 세 개까지 `report[0..2]`의 signed 값으로 변환합니다.

세 축 값과 버튼·hat 바이트를 합친 보고서 길이는 4바이트이며, 오류 처리 방식은 마우스 함수와 같습니다.

조이스틱 보고서 배치
바이트내용
0첫 번째 signed 값과 버튼 상위 비트
1두 번째 signed 값
2세 번째 signed 값
3상위 nibble 버튼, 하위 nibble hat 위치

예제의 4바이트 조이스틱 보고서입니다.

  static struct options jmod[] = {
	{.opt = "--b1",		.val = 0x10},
	{.opt = "--b2",		.val = 0x20},
	{.opt = "--b3",		.val = 0x40},
	{.opt = "--b4",		.val = 0x80},
	{.opt = "--hat1",	.val = 0x00},
	{.opt = "--hat2",	.val = 0x01},
	{.opt = "--hat3",	.val = 0x02},
	{.opt = "--hat4",	.val = 0x03},
	{.opt = "--hatneutral",	.val = 0x04},
	{.opt = NULL}
  };

  int joystick_fill_report(char report[8], char buf[BUF_LEN], int *hold)
  {
	char *tok = strtok(buf, " ");
	int mvt = 0;
	int i = 0;

	*hold = 1;

	/* set default hat position: neutral */
	report[3] = 0x04;

	for (; tok != NULL; tok = strtok(NULL, " ")) {

		if (strcmp(tok, "--quit") == 0)
			return -1;

		for (i = 0; jmod[i].opt != NULL; i++)
			if (strcmp(tok, jmod[i].opt) == 0) {
				report[3] = (report[3] & 0xF0) | jmod[i].val;
				break;
			}
		if (jmod[i].opt != NULL)
			continue;

		if (!(tok[0] == '-' && tok[1] == '-') && mvt < 3) {
			errno = 0;
			report[mvt++] = (char)strtol(tok, NULL, 0);
			if (errno != 0) {
				fprintf(stderr, "Bad value:'%s'\n", tok);
				report[mvt--] = 0;
			}
			continue;
		}

		fprintf(stderr, "unknown option: %s\n", tok);
	}
	return 4;
  }

print_options

336-367

`print_options()`는 선택된 모드의 첫 글자에 따라 사용 가능한 option과 value를 출력합니다.

키보드 모드에서는 `--hold`, modifier 목록, `[a-z]`와 `kval` 특수 키 목록을 보여 줍니다. 마우스 모드는 버튼과 두 signed number를, 조이스틱 모드는 버튼·hat option과 세 signed number를 안내합니다.

마우스와 조이스틱 안내에는 `--quit`로 프로그램을 종료한다는 설명도 포함됩니다.

  void print_options(char c)
  {
	int i = 0;

	if (c == 'k') {
		printf("	keyboard options:\n"
		       "		--hold\n");
		for (i = 0; kmod[i].opt != NULL; i++)
			printf("\t\t%s\n", kmod[i].opt);
		printf("\n	keyboard values:\n"
		       "		[a-z] or\n");
		for (i = 0; kval[i].opt != NULL; i++)
			printf("\t\t%-8s%s", kval[i].opt, i % 2 ? "\n" : "");
		printf("\n");
	} else if (c == 'm') {
		printf("	mouse options:\n"
		       "		--hold\n");
		for (i = 0; mmod[i].opt != NULL; i++)
			printf("\t\t%s\n", mmod[i].opt);
		printf("\n	mouse values:\n"
		       "		Two signed numbers\n"
		       "--quit to close\n");
	} else {
		printf("	joystick options:\n");
		for (i = 0; jmod[i].opt != NULL; i++)
			printf("\t\t%s\n", jmod[i].opt);
		printf("\n	joystick values:\n"
		       "		three signed numbers\n"
		       "--quit to close\n");
	}
  }

main 초기화와 장치 열기

368-397

`main()`은 장치 파일 이름, 파일 descriptor, 입력 buffer, 보고서 buffer, 전송 길이, hold 상태와 `select()`용 descriptor 집합을 준비합니다.

인수가 세 개보다 적으면 `devname mouse|keyboard|joystick` 형식의 사용법을 출력하고 1을 반환합니다. 두 번째 사용자 인수의 첫 문자가 `k`, `m`, `j` 중 하나가 아니면 2를 반환합니다.

장치 경로는 `argv[1]`에서 가져와 `O_RDWR`로 엽니다. 열기에 실패하면 장치 이름과 오류를 출력하고 3을 반환합니다.

장치를 연 뒤 선택한 모드의 option 목록을 출력하고 이벤트 처리 루프로 들어갑니다.

  int main(int argc, const char *argv[])
  {
	const char *filename = NULL;
	int fd = 0;
	char buf[BUF_LEN];
	int cmd_len;
	char report[8];
	int to_send = 8;
	int hold = 0;
	fd_set rfds;
	int retval, i;

	if (argc < 3) {
		fprintf(stderr, "Usage: %s devname mouse|keyboard|joystick\n",
			argv[0]);
		return 1;
	}

	if (argv[2][0] != 'k' && argv[2][0] != 'm' && argv[2][0] != 'j')
	  return 2;

	filename = argv[1];

	if ((fd = open(filename, O_RDWR, 0666)) == -1) {
		perror(filename);
		return 3;
	}

	print_options(argv[2][0]);

select 루프와 호스트 보고서 수신

398-419

무한 루프마다 `rfds`를 초기화하고 표준 입력과 HID 장치 descriptor를 모두 감시 대상으로 설정합니다.

`select()`가 signal로 중단되어 `EINTR`를 반환하면 다시 기다립니다. 그 밖의 오류는 `select()` 오류를 출력하고 4를 반환합니다.

HID 장치가 읽기 가능해지면 호스트가 보낸 보고서를 buffer로 읽고 각 바이트를 두 자리 16진수로 출력합니다. Caps Lock LED 상태 같은 출력 보고서가 이 경로로 표시됩니다.

표준 입력과 HID 장치를 한 `select()`에서 감시하므로 대화형 명령 입력과 호스트 응답을 어느 순서로든 처리할 수 있습니다.

이벤트 감시 루프
rfds 초기화STDIN_FILENO와 hidg fd 등록select()로 이벤트 대기hidg fd가 준비되면 호스트 보고서 readstdin이 준비되면 명령을 보고서로 변환오류나 종료 요청이 없으면 반복

표준 입력과 HID 장치의 준비 상태를 함께 처리합니다.

	while (42) {

		FD_ZERO(&rfds);
		FD_SET(STDIN_FILENO, &rfds);
		FD_SET(fd, &rfds);

		retval = select(fd + 1, &rfds, NULL, NULL, NULL);
		if (retval == -1 && errno == EINTR)
			continue;
		if (retval < 0) {
			perror("select()");
			return 4;
		}

		if (FD_ISSET(fd, &rfds)) {
			cmd_len = read(fd, buf, BUF_LEN - 1);
			printf("recv report:");
			for (i = 0; i < cmd_len; i++)
				printf(" %02x", buf[i]);
			printf("\n");
		}

명령 변환, 전송과 종료

420-457

표준 입력이 준비되면 한 줄을 읽습니다. EOF로 길이가 0이면 루프를 끝내고, 마지막 newline을 NUL로 바꾼 뒤 `hold`와 보고서 buffer를 초기화합니다.

선택한 모드에 따라 `keyboard_fill_report()`, `mouse_fill_report()`, `joystick_fill_report()`를 호출합니다. 함수가 `-1`을 반환하면 `--quit` 요청이므로 종료합니다.

만들어진 보고서를 `/dev/hidgX`에 기록하며, 요청한 길이만큼 모두 쓰지 못하면 오류를 출력하고 5를 반환합니다.

`hold`가 설정되지 않았다면 같은 길이의 0 보고서를 즉시 한 번 더 보내 key나 button release를 표현합니다. release 보고서 쓰기에 실패하면 6을 반환합니다.

루프를 벗어나면 장치 descriptor를 닫고 0을 반환합니다. 이 구조는 press 보고서 뒤에 release 보고서를 보내는 HID 입력의 기본 동작을 명확히 보여 줍니다.

main 반환 코드
반환값원인
0정상 종료
1인수 부족
2지원하지 않는 모드
3hidg 장치 열기 실패
4select 실패
5입력 보고서 write 실패
6release 보고서 write 실패

예제에서 구분하는 종료 원인입니다.

		if (FD_ISSET(STDIN_FILENO, &rfds)) {
			memset(report, 0x0, sizeof(report));
			cmd_len = read(STDIN_FILENO, buf, BUF_LEN - 1);

			if (cmd_len == 0)
				break;

			buf[cmd_len - 1] = '\0';
			hold = 0;

			memset(report, 0x0, sizeof(report));
			if (argv[2][0] == 'k')
				to_send = keyboard_fill_report(report, buf, &hold);
			else if (argv[2][0] == 'm')
				to_send = mouse_fill_report(report, buf, &hold);
			else
				to_send = joystick_fill_report(report, buf, &hold);

			if (to_send == -1)
				break;

			if (write(fd, report, to_send) != to_send) {
				perror(filename);
				return 5;
			}
			if (!hold) {
				memset(report, 0x0, sizeof(report));
				if (write(fd, report, to_send) != to_send) {
					perror(filename);
					return 6;
				}
			}
		}
	}

	close(fd);
	return 0;
  }