요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
===========================
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/
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::
#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.
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.
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.
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.
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}
};
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}
};
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;
}
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;
}
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;
}
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");
}
}
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]);
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");
}
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;
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
소개
1-15HID Gadget 드라이버는 USB Human Interface Device(HID)를 에뮬레이션합니다. HID 프로토콜의 기본 처리는 커널이 담당하고, 사용자 공간은 `/dev/hidgX` 문자 장치를 읽고 쓰는 방식으로 HID 보고서를 송수신합니다.
따라서 가젯 쪽 프로그램은 USB 전송 자체를 직접 구현하지 않고 보고서 형식에 맞는 바이트를 문자 장치에 기록하거나 호스트가 보낸 출력 보고서를 읽으면 됩니다.
HID 규격의 자세한 내용은 원문에 제시된 USB-IF 개발자 페이지 `https://www.usb.org/developers/hidpage/`를 참고합니다.
커널의 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 수입니다.
예제의 구조체 필드와 의미를 정리했습니다.
예제 설명자가 정의하는 입력 보고서의 바이트 배치입니다.
#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-87configfs로 조합한 가젯의 일부로 HID를 사용할 때는 데이터를 커널에 넘기기 위한 가짜 플랫폼 장치와 드라이버를 추가할 필요가 없습니다.
대신 `hidg_func_descriptor.report_desc`에 해당하는 보고서 설명자 바이트 스트림을 적절한 configfs 속성에 기록합니다. 커널은 그 속성에서 HID 기능 설명자를 구성합니다.
이 방식은 HID 기능을 다른 USB function과 함께 configfs 가젯 구성 안에서 동적으로 조합할 때 사용합니다.
플랫폼 데이터 대신 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-104HID 보고서는 `/dev/hidgX` 문자 장치에 대한 `read`와 `write`로 송수신합니다. 아래의 예제 프로그램은 이 인터페이스를 직접 사용합니다.
`hid_gadget_test`는 HID Gadget 드라이버를 시험하는 작은 대화형 프로그램입니다. 첫 번째 인수로 `hidg` 장치 경로를, 두 번째 인수로 `keyboard`, `mouse`, `joystick` 중 하나를 지정합니다.
예를 들어 `hid_gadget_test /dev/hidg0 keyboard`를 실행하면 키보드 모드 프롬프트가 열립니다. 프로그램 시작 시 사용할 수 있는 option과 value 목록이 출력됩니다.
키보드 모드에서는 한 보고서에 최대 여섯 개의 일반 키 값을 넣을 수 있으며 modifier option은 별도의 첫 바이트에 결합됩니다.
장치 유형에 따라 보고서 작성 함수와 입력 형식이 달라집니다.
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`로 전달되는지 확인합니다.
키 입력 보고서와 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로 동시에 표현할 수 있습니다.
`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을 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을 반환합니다.
예제의 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바이트이며, 오류 처리 방식은 마우스 함수와 같습니다.
예제의 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()`에서 감시하므로 대화형 명령 입력과 호스트 응답을 어느 순서로든 처리할 수 있습니다.
표준 입력과 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 입력의 기본 동작을 명확히 보여 줍니다.
예제에서 구분하는 종료 원인입니다.
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;
}
요약·해설
gadget_hid.rst:1-457HID Gadget 드라이버는 커널이 USB HID 전송을 처리하고 사용자 공간이 `/dev/hidgX`에 정해진 크기의 보고서를 읽고 쓰는 구조입니다. 플랫폼 장치 또는 configfs로 보고서 설명자를 제공한 뒤 키보드·마우스·조이스틱 기능을 구성할 수 있습니다.
문서의 `hid_gadget_test` 예제는 표준 입력과 HID 장치를 `select()`로 동시에 감시합니다. 사용자 명령을 장치별 보고서로 바꾸어 전송하고, `--hold`가 없으면 0 보고서를 추가해 release를 표현하며, 호스트가 보낸 LED 상태 등의 출력 보고서도 16진수로 표시합니다.
실사용에서는 보고서 설명자의 `report_length`와 사용자 공간 프로그램이 쓰는 길이·바이트 배치가 반드시 일치해야 합니다. 또한 여러 HID function을 만들 때는 UDC의 interrupt endpoint 수가 실제 개수 제한이 됩니다.